Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make trace_id and span_id types binaries instead of integers #590

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions apps/opentelemetry/src/otel_id_generator.erl
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ generate_span_id(Module) ->
%% @doc Generates a 128 bit random integer to use as a trace id.
-spec generate_trace_id() -> opentelemetry:trace_id().
generate_trace_id() ->
rand:uniform(?assert_type(2 bsl 127 - 1, pos_integer())). %% 2 shifted left by 127 == 2 ^ 128
rand:bytes(16).

%% @doc Generates a 64 bit random integer to use as a span id.
-spec generate_span_id() -> opentelemetry:span_id().
generate_span_id() ->
rand:uniform(?assert_type(2 bsl 63 - 1, pos_integer())). %% 2 shifted left by 63 == 2 ^ 64
rand:bytes(8).
4 changes: 2 additions & 2 deletions apps/opentelemetry/src/otel_links.erl
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ create_links([L | Rest], Limit, AttributePerLinkLimit, AttributeValueLengthLimit
%%

new_link({TraceId, SpanId, Attributes, TraceState}, AttributePerLinkLimit, AttributeValueLengthLimit)
when is_integer(TraceId),
is_integer(SpanId),
when is_binary(TraceId),
is_binary(SpanId),
(is_list(Attributes) orelse is_map(Attributes)),
is_list(TraceState) ->
#link{trace_id=TraceId,
Expand Down
4 changes: 2 additions & 2 deletions apps/opentelemetry/src/otel_resource_detector.erl
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ add_service_instance(Resource) ->
false ->
case erlang:node() of
nonode@nohost ->
ServiceInstanceId = otel_id_generator:generate_trace_id(),
ServiceInstanceId = otel_utils:encode_hex(otel_id_generator:generate_trace_id()),
ServiceInstanceResource = otel_resource:create([{?SERVICE_INSTANCE_ID, ServiceInstanceId}]),
otel_resource:merge(ServiceInstanceResource, Resource);
ServiceInstance ->
Expand All @@ -263,7 +263,7 @@ add_service_instance(Resource) ->
ServiceInstanceResource = otel_resource:create([{?SERVICE_INSTANCE_ID, ServiceInstance1}]),
otel_resource:merge(ServiceInstanceResource, Resource);
_Match ->
ServiceInstanceId = otel_id_generator:generate_trace_id(),
ServiceInstanceId = otel_utils:encode_hex(otel_id_generator:generate_trace_id()),
ServiceInstanceResource = otel_resource:create([{?SERVICE_INSTANCE_ID, ServiceInstanceId}]),
otel_resource:merge(ServiceInstanceResource, Resource)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ decide(undefined, _IdUpperBound) ->
?DROP;
decide(0, _IdUpperBound) ->
?DROP;
decide(TraceId, IdUpperBound) ->
Lower64Bits = TraceId band ?MAX_VALUE,
case erlang:abs(Lower64Bits) < IdUpperBound of
decide(<<_:65, LowerBits:63>>, IdUpperBound) ->
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I uh.. don't know why this is only 63 bits.. I just know that is what equaled the value I'd get with TraceId band ?MAX_VALUE which I copied from somewhere else..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is there any kind of spec explaining this specific behavior? I guess this is just because ?MAX_VALUE is the max 64 bits signed integer value (2^63-1) so that's unsurprising you'd look at the 63 lower bits, but I have no idea where the MAX_VALUE requirement comes from.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, the sign!

Trying to find now where it comes from.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may need to change (not in this PR) because the w3c spec now specifies 56 bits to be random if the new random trace id flag is set: https://w3c.github.io/trace-context/#random-trace-id-flag

case erlang:abs(LowerBits) < IdUpperBound of
true -> ?RECORD_AND_SAMPLE;
false -> ?DROP
end.
16 changes: 8 additions & 8 deletions apps/opentelemetry/test/opentelemetry_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ shutdown_sdk_noop_spans(_Config) ->

SpanCtx2 = ?start_span(<<"span-2">>),

?assertMatch(#span_ctx{trace_id=0,
span_id=0}, SpanCtx2),
?assertMatch(#span_ctx{trace_id = <<0:128>>,
span_id = <<0:64>>}, SpanCtx2),

?assertEqual(false, otel_span:set_attribute(SpanCtx1, a, 1)),

Expand Down Expand Up @@ -784,17 +784,17 @@ stop_temporary_app(_Config) ->
Tracer = opentelemetry:get_tracer(),

SpanCtx1 = ?start_span(<<"span-1">>),
?assertNotMatch(#span_ctx{trace_id=0,
span_id=0}, SpanCtx1),
?assertNotMatch(#span_ctx{trace_id = <<0:128>>,
span_id = <<0:64>>}, SpanCtx1),

ok = application:stop(opentelemetry),

%% stopping opentelemetry deletes the active span ETS table
%% so eventually newly started spans will be no-ops because
%% inserting a new span will fail
?UNTIL(case ?start_span(<<"span-2">>) of
#span_ctx{trace_id=0,
span_id=0} ->
#span_ctx{trace_id = <<0:128>>,
span_id = <<0:64>>} ->
true;
_ ->
false
Expand Down Expand Up @@ -1074,8 +1074,8 @@ too_many_attributes(Config) ->
disabled_sdk(_Config) ->
SpanCtx1 = ?start_span(<<"span-1">>),

?assertMatch(#span_ctx{trace_id=0,
span_id=0}, SpanCtx1),
?assertMatch(#span_ctx{trace_id = <<0:128>>,
span_id = <<0:64>>}, SpanCtx1),
ok.

no_exporter(_Config) ->
Expand Down
4 changes: 2 additions & 2 deletions apps/opentelemetry/test/otel_propagation_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ propagation(Config) ->

Headers = otel_propagator_text_map:inject([{<<"existing-header">>, <<"I exist">>}]),

EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]),
EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]),
EncodedTraceId = otel_utils:encode_hex(TraceId),
EncodedSpanId = otel_utils:encode_hex(SpanId),

?assertListsEqual([{<<"baggage">>, <<"key-2=value-2;metadata;md-k-1=md-v-1,key-1=value%3D1">>},
{<<"existing-header">>, <<"I exist">>} |
Expand Down
20 changes: 10 additions & 10 deletions apps/opentelemetry_api/src/opentelemetry.erl
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@

-type instrumentation_scope() :: #instrumentation_scope{}.

-type trace_id() :: non_neg_integer().
-type span_id() :: non_neg_integer().
-type trace_id() :: binary().
-type span_id() :: binary().

-type hex_trace_id() :: binary().
-type hex_span_id() :: binary().
Expand Down Expand Up @@ -336,20 +336,20 @@ convert_timestamp(Timestamp, Unit) ->
Attributes :: attributes_map(),
TraceState :: tracestate().
links(List) when is_list(List) ->
lists:filtermap(fun({TraceId, SpanId, Attributes, TraceState}) when is_integer(TraceId) ,
is_integer(SpanId) ,
lists:filtermap(fun({TraceId, SpanId, Attributes, TraceState}) when is_binary(TraceId) ,
is_binary(SpanId) ,
is_list(TraceState) ->
link_or_false(TraceId, SpanId, otel_span:process_attributes(Attributes), TraceState);
({#span_ctx{trace_id=TraceId,
span_id=SpanId,
tracestate=TraceState}, Attributes}) when is_integer(TraceId) ,
is_integer(SpanId) ,
tracestate=TraceState}, Attributes}) when is_binary(TraceId) ,
is_binary(SpanId) ,
is_list(TraceState) ->
link_or_false(TraceId, SpanId, otel_span:process_attributes(Attributes), TraceState);
(#span_ctx{trace_id=TraceId,
span_id=SpanId,
tracestate=TraceState}) when is_integer(TraceId) ,
is_integer(SpanId) ,
tracestate=TraceState}) when is_binary(TraceId) ,
is_binary(SpanId) ,
is_list(TraceState) ->
link_or_false(TraceId, SpanId, [], TraceState);
(_) ->
Expand All @@ -375,8 +375,8 @@ link(_, _) ->
SpanId :: span_id(),
Attributes :: attributes_map(),
TraceState :: tracestate().
link(TraceId, SpanId, Attributes, TraceState) when is_integer(TraceId),
is_integer(SpanId),
link(TraceId, SpanId, Attributes, TraceState) when is_binary(TraceId),
is_binary(SpanId),
(is_list(Attributes) orelse is_map(Attributes)),
is_list(TraceState) ->
#{trace_id => TraceId,
Expand Down
21 changes: 11 additions & 10 deletions apps/opentelemetry_api/src/otel_propagator_b3multi.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ inject(Ctx, Carrier, CarrierSet, _Options) ->
case otel_tracer:current_span_ctx(Ctx) of
#span_ctx{trace_id=TraceId,
span_id=SpanId,
trace_flags=TraceOptions} when TraceId =/= 0, SpanId =/= 0 ->
trace_flags=TraceOptions} when TraceId =/= <<0:128>>, SpanId =/= <<0:64>> ->
Options = case TraceOptions band 1 of 1 -> <<"1">>; _ -> <<"0">> end,
EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]),
EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]),
EncodedTraceId = otel_utils:encode_hex(TraceId),
EncodedSpanId = otel_utils:encode_hex(SpanId),
case {unicode:characters_to_binary(EncodedTraceId),
unicode:characters_to_binary(EncodedSpanId)} of
{BinTraceId, BinSpanId} when is_binary(BinTraceId) , is_binary(BinSpanId) ->
Expand Down Expand Up @@ -92,17 +92,21 @@ extract(Ctx, Carrier, _CarrierKeysFun, CarrierGet, _Options) ->
% Trace ID is a 32 or 16 lower-hex character binary.
parse_trace_id(TraceId) when is_binary(TraceId) ->
case string:length(TraceId) =:= 32 orelse string:length(TraceId) =:= 16 of
true -> string_to_integer(TraceId, 16);
_ -> throw(invalid)
true ->
binary:decode_hex(TraceId);
_ ->
throw(invalid)
end;
parse_trace_id(_) ->
throw(invalid).

% Span ID is a 16 lower-hex character binary.
parse_span_id(SpanId) when is_binary(SpanId) ->
case string:length(SpanId) =:= 16 of
true -> string_to_integer(SpanId, 16);
_ -> throw(invalid)
true ->
binary:decode_hex(SpanId);
_ ->
throw(invalid)
end;
parse_span_id(_) ->
throw(invalid).
Expand Down Expand Up @@ -130,6 +134,3 @@ parse_is_sampled(undefined) ->
0;
parse_is_sampled(_) ->
throw(invalid).

string_to_integer(S, Base) when is_binary(S) ->
binary_to_integer(S, Base).
21 changes: 11 additions & 10 deletions apps/opentelemetry_api/src/otel_propagator_b3single.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ inject(Ctx, Carrier, CarrierSet, _Options) ->
case otel_tracer:current_span_ctx(Ctx) of
#span_ctx{trace_id=TraceId,
span_id=SpanId,
trace_flags=TraceOptions} when TraceId =/= 0, SpanId =/= 0 ->
trace_flags=TraceOptions} when TraceId =/= <<0:128>>, SpanId =/= <<0:64>> ->
Options = case TraceOptions band 1 of 1 -> <<"1">>; _ -> <<"0">> end,
EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]),
EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]),
EncodedTraceId = otel_utils:encode_hex(TraceId),
EncodedSpanId = otel_utils:encode_hex(SpanId),
B3Context = otel_utils:assert_to_binary([EncodedTraceId, "-",
EncodedSpanId, "-", Options]),
CarrierSet(?B3_CONTEXT_KEY, B3Context, Carrier);
Expand Down Expand Up @@ -103,17 +103,21 @@ decode_b3_context(_) ->
% Trace ID is a 32 or 16 lower-hex character binary.
parse_trace_id(TraceId) when is_binary(TraceId) ->
case string:length(TraceId) =:= 32 orelse string:length(TraceId) =:= 16 of
true -> string_to_integer(TraceId, 16);
_ -> throw(invalid)
true ->
binary:decode_hex(TraceId);
_ ->
throw(invalid)
end;
parse_trace_id(_) ->
throw(invalid).

% Span ID is a 16 lower-hex character binary.
parse_span_id(SpanId) when is_binary(SpanId) ->
case string:length(SpanId) =:= 16 of
true -> string_to_integer(SpanId, 16);
_ -> throw(invalid)
true ->
binary:decode_hex(SpanId);
_ ->
throw(invalid)
end;
parse_span_id(_) ->
throw(invalid).
Expand All @@ -136,6 +140,3 @@ parse_is_sampled(Sampled) when is_binary(Sampled) ->
end;
parse_is_sampled(_) ->
throw(invalid).

string_to_integer(S, Base) when is_binary(S) ->
binary_to_integer(S, Base).
12 changes: 6 additions & 6 deletions apps/opentelemetry_api/src/otel_propagator_trace_context.erl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ fields(_) ->
inject(Ctx, Carrier, CarrierSet, _Options) ->
case otel_tracer:current_span_ctx(Ctx) of
SpanCtx=#span_ctx{trace_id=TraceId,
span_id=SpanId} when TraceId =/= 0 andalso SpanId =/= 0 ->
span_id=SpanId} when TraceId =/= <<0:128>> andalso SpanId =/= <<0:64>> ->
{TraceParent, TraceState} = encode_span_ctx(SpanCtx),
Carrier1 = CarrierSet(?HEADER_KEY, TraceParent, Carrier),
case TraceState of
Expand Down Expand Up @@ -109,8 +109,8 @@ encode_span_ctx(#span_ctx{trace_id=TraceId,

encode_traceparent(TraceId, SpanId, TraceOptions) ->
Options = case TraceOptions band 1 of 1 -> <<"01">>; _ -> <<"00">> end,
{ok, EncodedTraceId} = otel_utils:format_binary_string("~32.16.0b", [TraceId]),
{ok, EncodedSpanId} = otel_utils:format_binary_string("~16.16.0b", [SpanId]),
EncodedTraceId = otel_utils:encode_hex(TraceId),
EncodedSpanId = otel_utils:encode_hex(SpanId),
otel_utils:assert_to_binary([?VERSION, "-", EncodedTraceId, "-",
EncodedSpanId, "-", Options]).

Expand Down Expand Up @@ -148,9 +148,9 @@ decode(_) ->
to_span_ctx(Version, TraceId, SpanId, Opts) ->
try
%% verify version is hexadecimal
_ = binary_to_integer(Version, 16),
otel_tracer:from_remote_span(?assert_type(binary_to_integer(TraceId, 16), non_neg_integer()),
?assert_type(binary_to_integer(SpanId, 16), non_neg_integer()),
_ = binary:decode_hex(Version),
otel_tracer:from_remote_span(binary:decode_hex(TraceId),
binary:decode_hex(SpanId),
case Opts of <<"01">> -> 1; <<"00">> -> 0; _ -> error(badarg) end)
catch
%% to integer from base 16 string failed
Expand Down
18 changes: 4 additions & 14 deletions apps/opentelemetry_api/src/otel_span.erl
Original file line number Diff line number Diff line change
Expand Up @@ -167,29 +167,19 @@ span_id(#span_ctx{span_id=SpanId}) ->
hex_span_ctx(#span_ctx{trace_id=TraceId,
span_id=SpanId,
trace_flags=TraceFlags}) ->
#{otel_trace_id => io_lib:format("~32.16.0b", [TraceId]),
otel_span_id => io_lib:format("~16.16.0b", [SpanId]),
#{otel_trace_id => otel_utils:encode_hex(TraceId),
otel_span_id => otel_utils:encode_hex(SpanId),
otel_trace_flags => case TraceFlags band 1 of 1 -> "01"; _ -> "00" end};
hex_span_ctx(_) ->
#{}.

-spec hex_trace_id(opentelemetry:span_ctx()) -> opentelemetry:hex_trace_id().
hex_trace_id(#span_ctx{trace_id=TraceId}) ->
case otel_utils:format_binary_string("~32.16.0b", [TraceId]) of
{ok, Binary} ->
Binary;
_ ->
<<>>
end.
otel_utils:encode_hex(TraceId).

-spec hex_span_id(opentelemetry:span_ctx()) -> opentelemetry:hex_span_id().
hex_span_id(#span_ctx{span_id=SpanId}) ->
case otel_utils:format_binary_string("~16.16.0b", [SpanId]) of
{ok, Binary} ->
Binary;
_ ->
<<>>
end.
otel_utils:encode_hex(SpanId).

-spec tracestate(opentelemetry:span_ctx() | undefined) -> opentelemetry:tracestate().
tracestate(#span_ctx{tracestate=Tracestate}) ->
Expand Down
9 changes: 6 additions & 3 deletions apps/opentelemetry_api/src/otel_tracer_noop.erl
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
-include("opentelemetry.hrl").
-include("otel_tracer.hrl").

-define(NOOP_SPAN_CTX, #span_ctx{trace_id=0,
span_id=0,
-define(ZERO_TRACE_ID, <<0:128>>).
-define(ZERO_SPAN_ID, <<0:64>>).

-define(NOOP_SPAN_CTX, #span_ctx{trace_id = ?ZERO_TRACE_ID,
span_id = ?ZERO_SPAN_ID,
trace_flags=0,
tracestate=[],
is_valid=false,
Expand All @@ -41,7 +44,7 @@
start_span(Ctx, _, _SpanName, _) ->
%% Spec: Behavior of the API in the absence of an installed SDK
case otel_tracer:current_span_ctx(Ctx) of
Parent=#span_ctx{trace_id=TraceId} when TraceId =/= 0 ->
Parent=#span_ctx{trace_id=TraceId} when TraceId =/= ?ZERO_TRACE_ID ->
Parent;
_ ->
%% If the parent Context contains no valid Span,
Expand Down
11 changes: 10 additions & 1 deletion apps/opentelemetry_api/src/otel_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
format_binary_string/2,
format_binary_string/3,
assert_to_binary/1,
unicode_to_binary/1]).
unicode_to_binary/1,
encode_hex/1]).

-if(?OTP_RELEASE >= 24).
format_exception(Kind, Reason, StackTrace) ->
Expand Down Expand Up @@ -56,3 +57,11 @@ unicode_to_binary(String) ->
_ ->
{error, bad_binary_conversion}
end.

-if(?OTP_RELEASE >= 26).
encode_hex(Bin) ->
binary:encode_hex(Bin, lowercase).
-else.
encode_hex(Bin) ->
string:lowercase(binary:encode_hex(Bin)).
-endif.
Loading