Skip to content

Commit 29b7477

Browse files
[Feature]: Add is_remote flag in exporter for spans and span links (#894)
* feat: added is_remote span flag functionality in exporter * chore: updated PR for changelog * fix: define start and end time in tests * fix: pass attributes in erlang sdk
1 parent ceae117 commit 29b7477

File tree

7 files changed

+131
-16
lines changed

7 files changed

+131
-16
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- [Add is_remote flag in exporter for spans and span links](https://github.com/open-telemetry/opentelemetry-erlang/pull/894)
13+
1014
## API 1.4.1 - 2025-07-31
1115

1216
### Fixes

apps/opentelemetry/include/otel_span.hrl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
%% 64 bit int parent span
2929
parent_span_id :: opentelemetry:span_id() | undefined | '_',
3030

31+
%% true if the parent span context came from a remote process
32+
parent_span_is_remote :: boolean() | undefined | '_',
33+
3134
%% name of the span
3235
name :: unicode:unicode_binary() | atom() | '_',
3336

apps/opentelemetry/src/otel_span_utils.erl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ start_span(Ctx, Name, Sampler, IdGenerator, Opts) ->
5050
new_span(Ctx, Name, Sampler, IdGenerator, StartTime, Kind, Attributes, Events, Links).
5151

5252
new_span(Ctx, Name, Sampler, IdGeneratorModule, StartTime, Kind, Attributes, Events, Links) ->
53-
{NewSpanCtx, ParentSpanId} = new_span_ctx(Ctx, IdGeneratorModule),
53+
{NewSpanCtx, ParentSpanId, ParentIsRemote} = new_span_ctx(Ctx, IdGeneratorModule),
5454

5555
TraceId = NewSpanCtx#span_ctx.trace_id,
5656
SpanId = NewSpanCtx#span_ctx.span_id,
@@ -63,6 +63,7 @@ new_span(Ctx, Name, Sampler, IdGeneratorModule, StartTime, Kind, Attributes, Eve
6363
tracestate=TraceState,
6464
start_time=StartTime,
6565
parent_span_id=ParentSpanId,
66+
parent_span_is_remote=ParentIsRemote,
6667
kind=Kind,
6768
name=Name,
6869
attributes=otel_attributes:set(SamplerAttributes, Attributes),
@@ -77,16 +78,16 @@ new_span(Ctx, Name, Sampler, IdGeneratorModule, StartTime, Kind, Attributes, Eve
7778
is_recording=IsRecording}, Span}.
7879

7980
-spec new_span_ctx(otel_ctx:t(), otel_id_generator:t()) ->
80-
{opentelemetry:span_ctx(), opentelemetry:span_id() | undefined}.
81+
{opentelemetry:span_ctx(), opentelemetry:span_id() | undefined, boolean() | undefined}.
8182
new_span_ctx(Ctx, IdGeneratorModule) ->
8283
case otel_tracer:current_span_ctx(Ctx) of
8384
undefined ->
84-
{root_span_ctx(IdGeneratorModule), undefined};
85+
{root_span_ctx(IdGeneratorModule), undefined, undefined};
8586
#span_ctx{is_valid=false} ->
86-
{root_span_ctx(IdGeneratorModule), undefined};
87-
ParentSpanCtx=#span_ctx{span_id=ParentSpanId} ->
87+
{root_span_ctx(IdGeneratorModule), undefined, undefined};
88+
ParentSpanCtx=#span_ctx{span_id=ParentSpanId, is_remote=ParentIsRemote} ->
8889
%% keep the rest of the parent span ctx, simply need to update the span_id
89-
{ParentSpanCtx#span_ctx{span_id=IdGeneratorModule:generate_span_id()}, ParentSpanId}
90+
{ParentSpanCtx#span_ctx{span_id=IdGeneratorModule:generate_span_id()}, ParentSpanId, ParentIsRemote}
9091
end.
9192

9293
root_span_ctx(IdGeneratorModule) ->

apps/opentelemetry/test/otel_batch_processor_SUITE.erl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,18 @@ shutdown(_) ->
9999
%% helpers
100100

101101
generate_span() ->
102+
StartTime = opentelemetry:timestamp(),
103+
EndTime = opentelemetry:timestamp(),
102104
#span{trace_id = otel_id_generator:generate_trace_id(),
103105
span_id = otel_id_generator:generate_span_id(),
104106
name = "test_span",
107+
start_time = StartTime,
108+
end_time = EndTime,
105109
trace_flags = 1,
106110
is_recording = true,
111+
parent_span_is_remote = undefined,
112+
attributes = otel_attributes:new([], 128, 128),
113+
events = otel_events:new(128, 128, 128),
114+
links = otel_links:new([], 128, 128, 128),
115+
tracestate = otel_tracestate:new([]),
107116
instrumentation_scope = #instrumentation_scope{name = "test"}}.

apps/opentelemetry_exporter/src/otel_otlp_traces.erl

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
%% for testing
2323
-ifdef(TEST).
2424
-export([to_proto_by_instrumentation_scope/1,
25-
to_proto/1]).
25+
to_proto/1,
26+
build_span_flags/2]).
2627
-endif.
2728

2829
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
@@ -67,6 +68,7 @@ to_proto(#span{trace_id=TraceId,
6768
span_id=SpanId,
6869
tracestate=TraceState,
6970
parent_span_id=MaybeParentSpanId,
71+
parent_span_is_remote=ParentIsRemote,
7072
name=Name,
7173
kind=Kind,
7274
start_time=StartTime,
@@ -75,7 +77,7 @@ to_proto(#span{trace_id=TraceId,
7577
events=TimedEvents,
7678
links=Links,
7779
status=Status,
78-
trace_flags=_TraceFlags,
80+
trace_flags=TraceFlags,
7981
is_recording=_IsRecording}) when is_integer(TraceId),
8082
is_integer(SpanId) ->
8183
ParentSpanId = case MaybeParentSpanId of _ when is_integer(MaybeParentSpanId) -> <<MaybeParentSpanId:64>>; _ -> <<>> end,
@@ -93,7 +95,8 @@ to_proto(#span{trace_id=TraceId,
9395
dropped_events_count => otel_events:dropped(TimedEvents),
9496
links => to_links(otel_links:list(Links)),
9597
dropped_links_count => otel_links:dropped(Links),
96-
status => to_status(Status)}.
98+
status => to_status(Status),
99+
flags => build_span_flags(ParentIsRemote, TraceFlags)}.
97100

98101
-spec to_status(opentelemetry:status() | undefined) -> opentelemetry_exporter_trace_service_pb:status().
99102
to_status(#status{code=Code,
@@ -118,10 +121,11 @@ to_links(Links) ->
118121
span_id => <<SpanId:64>>,
119122
trace_state => otel_tracestate:encode_header(TraceState),
120123
attributes => otel_otlp_common:to_attributes(Attributes),
121-
dropped_attributes_count => 0} || #link{trace_id=TraceId,
122-
span_id=SpanId,
123-
attributes=Attributes,
124-
tracestate=TraceState} <- Links].
124+
dropped_attributes_count => 0,
125+
flags => build_span_flags(undefined, 0)} || #link{trace_id=TraceId,
126+
span_id=SpanId,
127+
attributes=Attributes,
128+
tracestate=TraceState} <- Links].
125129

126130
-spec to_otlp_kind(atom()) -> opentelemetry_exporter_trace_service_pb:'span.SpanKind'().
127131
to_otlp_kind(?SPAN_KIND_INTERNAL) ->
@@ -144,3 +148,21 @@ to_otlp_status(?OTEL_STATUS_OK) ->
144148
'STATUS_CODE_OK';
145149
to_otlp_status(?OTEL_STATUS_ERROR) ->
146150
'STATUS_CODE_ERROR'.
151+
152+
%% @doc Builds span flags based on the parent span context's remote property.
153+
%% This follows the OTLP specification for span flags.
154+
%% @param ParentIsRemote boolean() | undefined - whether the parent span context is remote
155+
%% @param BaseFlags integer() - base flags (typically trace_flags)
156+
%% @return integer() - computed span flags
157+
-spec build_span_flags(boolean() | undefined, integer()) -> integer().
158+
build_span_flags(ParentIsRemote, BaseFlags) ->
159+
%% SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK = 0x100
160+
%% SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK = 0x200
161+
HasIsRemoteMask = 16#100,
162+
IsRemoteMask = 16#200,
163+
164+
Flags = BaseFlags bor HasIsRemoteMask,
165+
case ParentIsRemote of
166+
true -> Flags bor IsRemoteMask;
167+
_ -> Flags
168+
end.

apps/opentelemetry_exporter/test/opentelemetry_exporter_SUITE.erl

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ all() ->
1414
{group, grpc}, {group, grpc_gzip}].
1515

1616
groups() ->
17-
[{functional, [], [configuration, span_round_trip, ets_instrumentation_info, to_attributes]},
17+
[{functional, [], [configuration, span_round_trip, span_flags, ets_instrumentation_info, to_attributes]},
1818
{grpc, [], [verify_export]},
1919
{grpc_gzip, [], [verify_export]},
2020
{http_protobuf, [], [verify_export, user_agent]},
@@ -322,6 +322,7 @@ span_round_trip(_Config) ->
322322
], 128, 128),
323323
status = #status{code=?OTEL_STATUS_OK,
324324
message = <<"">>},
325+
parent_span_is_remote = undefined,
325326
instrumentation_scope = #instrumentation_scope{name = <<"tracer-1">>,
326327
version = <<"0.0.1">>}},
327328

@@ -335,6 +336,79 @@ span_round_trip(_Config) ->
335336

336337
ok.
337338

339+
span_flags(_Config) ->
340+
%% Test build_span_flags function
341+
?assertEqual(16#100, otel_otlp_traces:build_span_flags(false, 0)), %% 0x100 - local parent
342+
?assertEqual(16#300, otel_otlp_traces:build_span_flags(true, 0)), %% 0x300 - remote parent
343+
?assertEqual(16#100, otel_otlp_traces:build_span_flags(undefined, 0)), %% 0x100 - no parent
344+
?assertEqual(16#101, otel_otlp_traces:build_span_flags(false, 1)), %% 0x101 - local parent with sampled flag
345+
?assertEqual(16#301, otel_otlp_traces:build_span_flags(true, 1)), %% 0x301 - remote parent with sampled flag
346+
347+
%% Test span with local parent
348+
TraceId = otel_id_generator:generate_trace_id(),
349+
SpanId = otel_id_generator:generate_span_id(),
350+
ParentSpanId = otel_id_generator:generate_span_id(),
351+
StartTime = opentelemetry:timestamp(),
352+
EndTime = opentelemetry:timestamp(),
353+
354+
LocalParentSpan = #span{name = <<"span-with-local-parent">>,
355+
trace_id = TraceId,
356+
span_id = SpanId,
357+
parent_span_id = ParentSpanId,
358+
parent_span_is_remote = false,
359+
kind = ?SPAN_KIND_CLIENT,
360+
start_time = StartTime,
361+
end_time = EndTime,
362+
trace_flags = 1,
363+
is_recording = true,
364+
attributes = otel_attributes:new([], 128, 128),
365+
events = otel_events:new(128, 128, 128),
366+
links = otel_links:new([], 128, 128, 128),
367+
tracestate = otel_tracestate:new([])},
368+
369+
PbSpanLocal = otel_otlp_traces:to_proto(LocalParentSpan),
370+
?assertEqual(16#101, maps:get(flags, PbSpanLocal)), %% 0x101 - local parent with sampled flag
371+
372+
%% Test span with remote parent
373+
RemoteParentSpan = #span{name = <<"span-with-remote-parent">>,
374+
trace_id = TraceId,
375+
span_id = SpanId,
376+
parent_span_id = ParentSpanId,
377+
parent_span_is_remote = true,
378+
kind = ?SPAN_KIND_CLIENT,
379+
start_time = StartTime,
380+
end_time = EndTime,
381+
trace_flags = 1,
382+
is_recording = true,
383+
attributes = otel_attributes:new([], 128, 128),
384+
events = otel_events:new(128, 128, 128),
385+
links = otel_links:new([], 128, 128, 128),
386+
tracestate = otel_tracestate:new([])},
387+
388+
PbSpanRemote = otel_otlp_traces:to_proto(RemoteParentSpan),
389+
?assertEqual(16#301, maps:get(flags, PbSpanRemote)), %% 0x301 - remote parent with sampled flag
390+
391+
%% Test span with no parent
392+
NoParentSpan = #span{name = <<"span-with-no-parent">>,
393+
trace_id = TraceId,
394+
span_id = SpanId,
395+
parent_span_id = undefined,
396+
parent_span_is_remote = undefined,
397+
kind = ?SPAN_KIND_CLIENT,
398+
start_time = StartTime,
399+
end_time = EndTime,
400+
trace_flags = 1,
401+
is_recording = true,
402+
attributes = otel_attributes:new([], 128, 128),
403+
events = otel_events:new(128, 128, 128),
404+
links = otel_links:new([], 128, 128, 128),
405+
tracestate = otel_tracestate:new([])},
406+
407+
PbSpanNoParent = otel_otlp_traces:to_proto(NoParentSpan),
408+
?assertEqual(16#101, maps:get(flags, PbSpanNoParent)), %% 0x101 - no parent with sampled flag
409+
410+
ok.
411+
338412
%% test conversion of attributes to the map representation of the OTLP protobuf
339413
to_attributes(_Config) ->
340414
%% this tests the removal of support for iolists as values in attributes

apps/opentelemetry_zipkin/test/opentelemetry_zipkin_SUITE.erl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ verify_export(_Config) ->
4646
{<<"map-key-1">>, #{<<"map-key-1">> => 123}},
4747
{<<"list-key-1">>, [3.14, 9.345]}
4848
], 128, 128),
49-
status=opentelemetry:status(?SPAN_KIND_INTERNAL, <<"some message about status">>)},
49+
status=opentelemetry:status(?SPAN_KIND_INTERNAL, <<"some message about status">>),
50+
parent_span_is_remote = undefined},
5051
true = ets:insert(Tid, ParentSpan),
5152

5253
ChildSpan = #span{name = <<"span-2">>,
@@ -66,7 +67,8 @@ verify_export(_Config) ->
6667
{attr_3, true},
6768
{<<"map-key-1">>, #{<<"map-key-1">> => 123}},
6869
{<<"list-key-1">>, [3.14, 9.345]}
69-
], 128, 128)},
70+
], 128, 128),
71+
parent_span_is_remote = false},
7072
true = ets:insert(Tid, ChildSpan),
7173

7274
?assertMatch(ok, opentelemetry_zipkin:export(traces, Tid, Resource, State)),

0 commit comments

Comments
 (0)