Skip to content

Commit

Permalink
add http propagators for trace context
Browse files Browse the repository at this point in the history
  • Loading branch information
tsloughter committed Nov 16, 2019
1 parent 21d75b9 commit b9024d1
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 13 deletions.
7 changes: 7 additions & 0 deletions src/opentelemetry_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
start(_StartType, _StartArgs) ->
Opts = application:get_all_env(opentelemetry),
opentelemetry:set_default_context_manager(ot_ctx_pdict),

{BaggageHttpExtractor, BaggageHttpInjector} = ot_baggage:get_http_propagators(),
{CorrelationsHttpExtractor, CorrelationsHttpInjector} = ot_correlations:get_http_propagators(),
{B3HttpExtractor, B3HttpInjector} = ot_tracer_default:b3_propagators(),
opentelemetry:set_http_extractor([BaggageHttpExtractor, CorrelationsHttpExtractor, B3HttpExtractor]),
opentelemetry:set_http_injector([BaggageHttpInjector, CorrelationsHttpInjector, B3HttpInjector]),

opentelemetry_sup:start_link(Opts).

stop(_State) ->
Expand Down
10 changes: 8 additions & 2 deletions src/ot_ctx_seqtrace.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@
-include_lib("kernel/include/logger.hrl").

%% needed until type specs for seq_trace are fixed
%% -dialyzer({nowarn_function, get_value/2}).
%% -dialyzer({nowarn_function, set_value/2}).
-dialyzer({nowarn_function, get_value/2}).
-dialyzer({nowarn_function, get_value/3}).
-dialyzer({nowarn_function, set_value/3}).
-dialyzer({nowarn_function, clear/1}).
-dialyzer({nowarn_function, remove/2}).
-dialyzer({nowarn_function, get_current/1}).
-dialyzer({nowarn_function, set_current/2}).
-dialyzer({nowarn_function, get_context/0}).

-spec set_value(term(), term(), term()) -> ok.
set_value(Namespace, Key, Value) ->
Expand Down
110 changes: 110 additions & 0 deletions src/ot_propagation_http_b3.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
%%%------------------------------------------------------------------------
%% Copyright 2019, OpenTelemetry Authors
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @doc
%% @end
%%%-----------------------------------------------------------------------
-module(ot_propagation_http_b3).

-export([inject/2,
extract/2]).

-include_lib("opentelemetry_api/include/opentelemetry.hrl").

-define(B3_TRACE_ID, <<"X-B3-TraceId">>).
-define(B3_SPAN_ID, <<"X-B3-SpanId">>).
-define(B3_SAMPLED, <<"X-B3-Sampled">>).

-define(IS_SAMPLED(S), S =:= "1" orelse S =:= <<"1">> orelse S =:= "true" orelse S =:= <<"true">>).

-spec inject(ot_propagation:http_headers(),
{opencensus:span_ctx(), opentelemetry:span_ctx() | undefined} | undefined)
-> ot_propagation:http_headers().
inject(_, {#span_ctx{trace_id=TraceId,
span_id=SpanId}, _}) when TraceId =:= 0
; SpanId =:= 0 ->
[];
inject(_, {#span_ctx{trace_id=TraceId,
span_id=SpanId,
trace_flags=TraceOptions}, _}) ->
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]),
[{?B3_TRACE_ID, EncodedTraceId},
{?B3_SPAN_ID, EncodedSpanId},
{?B3_SAMPLED, Options}];
inject(_, undefined) ->
[].

-spec extract(ot_propagation:http_headers(), term()) -> opentelemetry:span_ctx()| undefined.
extract(Headers, _) when is_list(Headers) ->
try
TraceId = trace_id(Headers),
SpanId = span_id(Headers),
Sampled = lookup(?B3_SAMPLED, Headers),
{#span_ctx{trace_id=string_to_integer(TraceId, 16),
span_id=string_to_integer(SpanId, 16),
trace_flags=case Sampled of True when ?IS_SAMPLED(True) -> 1; _ -> 0 end}, undefined}
catch
throw:invalid ->
undefined;

%% thrown if _to_integer fails
error:badarg ->
undefined
end;
extract(_, _) ->
undefined.

trace_id(Headers) ->
case lookup(?B3_TRACE_ID, Headers) of
TraceId when is_list(TraceId) orelse is_binary(TraceId) ->
case string:length(TraceId) =:= 32 orelse string:length(TraceId) =:= 16 of
true ->
TraceId;
_ ->
throw(invalid)
end;
_ ->
throw(invalid)
end.

span_id(Headers) ->
case lookup(?B3_SPAN_ID, Headers) of
SpanId when is_list(SpanId) orelse is_binary(SpanId) ->
case string:length(SpanId) =:= 16 of
true ->
SpanId;
_ ->
throw(invalid)
end;
_ ->
throw(invalid)
end.

%% find a header in a list, ignoring case
lookup(_, []) ->
undefined;
lookup(Header, [{H, Value} | Rest]) ->
case string:equal(Header, H, true, none) of
true ->
Value;
false ->
lookup(Header, Rest)
end.

string_to_integer(S, Base) when is_binary(S) ->
binary_to_integer(S, Base);
string_to_integer(S, Base) when is_list(S) ->
list_to_integer(S, Base).
133 changes: 133 additions & 0 deletions src/ot_propagation_http_w3c.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
%%%------------------------------------------------------------------------
%% Copyright 2019, OpenTelemetry Authors
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% @doc
%% @end
%%%-----------------------------------------------------------------------
-module(ot_propagation_http_w3c).

-export([inject/2,
encode/1,
extract/2,
decode/1]).

-include_lib("opentelemetry_api/include/opentelemetry.hrl").

-define(VERSION, "00").

-define(ZERO_TRACEID, <<"00000000000000000000000000000000">>).
-define(ZERO_SPANID, <<"0000000000000000">>).

-define(HEADER_KEY, <<"traceparent">>).
-define(STATE_HEADER_KEY, <<"tracestate">>).

-spec inject(ot_propagation:http_headers(),
{opentelemetry:span_ctx(), opentelemetry:span_ctx() | undefined} | undefined)
-> ot_propagation:http_headers().
inject(_, {#span_ctx{trace_id=TraceId,
span_id=SpanId}, _})
when TraceId =:= 0 orelse SpanId =:= 0 ->
[];
inject(_, {SpanCtx=#span_ctx{}, _}) ->
EncodedValue = encode(SpanCtx),
[{?HEADER_KEY, EncodedValue} | encode_tracestate(SpanCtx)];
inject(_, undefined) ->
[].

-spec encode(opencensus:span_ctx()) -> iolist().
encode(#span_ctx{trace_id=TraceId,
span_id=SpanId,
trace_flags=TraceOptions}) ->
Options = case TraceOptions band 1 of 1 -> <<"01">>; _ -> <<"00">> end,
EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]),
EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]),
[?VERSION, "-", EncodedTraceId, "-", EncodedSpanId, "-", Options].

encode_tracestate(#span_ctx{tracestate=undefined}) ->
[];
encode_tracestate(#span_ctx{tracestate=Entries}) ->
StateHeaderValue = lists:join($,, [[Key, $=, Value] || {Key, Value} <- Entries]),
[{?STATE_HEADER_KEY, StateHeaderValue}].

-spec extract(ot_propagation:http_headers(), term()) -> opentelemetry:span_ctx() | undefined.
extract(Headers, _) when is_list(Headers) ->
case lists:keyfind(?HEADER_KEY, 1, Headers) of
{_, Value} ->
case decode(Value) of
undefined ->
undefined;
SpanCtx ->
Tracestate = tracestate_from_headers(Headers),
SpanCtx#span_ctx{tracestate=Tracestate}
end;
_ ->
undefined
end;
extract(_, _) ->
undefined.

tracestate_from_headers(Headers) ->
%% could be multiple tracestate headers. Combine them all with comma separators
case combine_headers(?STATE_HEADER_KEY, Headers) of
[] ->
undefined;
FieldValue ->
tracestate_decode(FieldValue)
end.

combine_headers(Key, Headers) ->
lists:foldl(fun({K, V}, Acc) ->
case string:equal(K, Key) of
true ->
[V, $, | Acc];
false ->
Acc
end
end, [], Headers).

tracestate_decode(Value) ->
%% TODO: the 512 byte limit should not include optional white space that can
%% appear between list members.
case iolist_size(Value) of
Size when Size =< 512 ->
[split(Pair) || Pair <- string:lexemes(Value, [$,])];
_ ->
undefined
end.

split(Pair) ->
case string:split(Pair, "=") of
[Key, Value] ->
{iolist_to_binary(Key), iolist_to_binary(Value)};
[Key] ->
{iolist_to_binary(Key), <<>>}
end.

decode(TraceContext) when is_list(TraceContext) ->
decode(list_to_binary(TraceContext));
decode(<<?VERSION, "-", TraceId:32/binary, "-", SpanId:16/binary, _/binary>>)
when TraceId =:= ?ZERO_TRACEID orelse SpanId =:= ?ZERO_SPANID ->
undefined;
decode(<<?VERSION, "-", TraceId:32/binary, "-", SpanId:16/binary, "-", Opts:2/binary, _/binary>>) ->
try
#span_ctx{trace_id=binary_to_integer(TraceId, 16),
span_id=binary_to_integer(SpanId, 16),
trace_flags=case Opts of <<"01">> -> 1; _ -> 0 end}
catch
%% to integer from base 16 string failed
error:badarg ->
undefined
end;
decode(_) ->
undefined.
29 changes: 19 additions & 10 deletions src/ot_tracer_default.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
end_span/1,
on_end/1,
current_span_ctx/1,
get_binary_format/1,
get_http_text_format/1]).
b3_propagators/0,
w3c_propagators/0]).

%% tracer access functions
-export([span_module/1]).
Expand Down Expand Up @@ -68,7 +68,7 @@ with_span(_Tracer, SpanCtx) ->

-spec with_span(opentelemetry:tracer(), opentelemetry:span_ctx(), fun()) -> ok.
with_span(_Tracer, SpanCtx, Fun) ->
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, {SpanCtx, undefined}, Fun).
ot_ctx:with_value(?TRACER_KEY, ?SPAN_CTX, {SpanCtx, undefined}, Fun).

-spec current_span_ctx(opentelemetry:tracer()) -> opentelemetry:span_ctx().
current_span_ctx(_Tracer) ->
Expand All @@ -89,6 +89,22 @@ current_ctx() ->
span_module({_, #tracer{span_module=SpanModule}}) ->
SpanModule.

-spec b3_propagators() -> {ot_propagation:http_extractor(), ot_propagation:http_injector()}.
b3_propagators() ->
ToText = fun ot_propagation_http_b3:inject/2,
FromText = fun ot_propagation_http_b3:extract/2,
Injector = ot_ctx:http_injector(?TRACER_KEY, ?SPAN_CTX, ToText),
Extractor = ot_ctx:http_extractor(?TRACER_KEY, ?SPAN_CTX, FromText),
{Extractor, Injector}.

-spec w3c_propagators() -> {ot_propagation:http_extractor(), ot_propagation:http_injector()}.
w3c_propagators() ->
ToText = fun ot_propagation_http_w3c:inject/2,
FromText = fun ot_propagation_http_w3c:extract/2,
Injector = ot_ctx:http_injector(?TRACER_KEY, ?SPAN_CTX, ToText),
Extractor = ot_ctx:http_extractor(?TRACER_KEY, ?SPAN_CTX, FromText),
{Extractor, Injector}.

%%--------------------------------------------------------------------
%% @doc
%% Ends the span in the current pdict context. And sets the parent
Expand All @@ -103,10 +119,3 @@ end_span(Tracer) ->
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, ParentCtx),
ok.

-spec get_binary_format(opentelemetry:tracer()) -> binary().
get_binary_format(_) ->
<<>>.

-spec get_http_text_format(opentelemetry:tracer()) -> opentelemetry:http_headers().
get_http_text_format(_) ->
[].
24 changes: 23 additions & 1 deletion test/opentelemetry_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ all() ->
{group, ot_ctx_seqtrace}].

all_testcases() ->
[child_spans, update_span_data].
[child_spans, update_span_data, propagation].

groups() ->
[{ot_ctx_pdict, [shuffle], all_testcases()},
Expand Down Expand Up @@ -111,6 +111,28 @@ update_span_data(Config) ->
timed_events=TimedEvents,
_='_'})).

propagation(_Config) ->
SpanCtx1=#span_ctx{trace_id=TraceId,
span_id=SpanId} = otel:start_span(<<"span-1">>),
Headers = ot_propagation:http_inject([{<<"existing-header">>, <<"I exist">>}]),

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

?assertListsMatch([{<<"X-B3-TraceId">>, EncodedTraceId},
{<<"X-B3-SpanId">>, EncodedSpanId},
{<<"X-B3-Sampled">>, "1"},
{<<"existing-header">>, <<"I exist">>}], Headers),

otel:end_span(),

?assertEqual(undefined, otel:current_span_ctx()),

ot_propagation:http_extract(Headers),
?assertSpanCtxsEqual(SpanCtx1, otel:current_span_ctx()),

ok.

%%

assert_all_exported(Tid, SpanCtxs) ->
Expand Down
16 changes: 16 additions & 0 deletions test/ot_test_utils.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,19 @@
end
end)(0)).


-define(assertListsMatch(List1, List2), ?assertEqual(lists:sort(List1), lists:sort(List2))).

%% a macro for asserting the important parts of a span ctx are equal
%% parts we keep in the record like is_recorded is not propagated and
%% thus should not be part of a comparison to check propagated ctx
-define(assertSpanCtxsEqual(SpanCtx1, SpanCtx2), begin
#span_ctx{trace_id=TraceId1,
span_id=SpanId1,
trace_flags=TraceFlags1} = SpanCtx1,
#span_ctx{trace_id=TraceId2,
span_id=SpanId2,
trace_flags=TraceFlags2} = SpanCtx2,
?assertEqual({TraceId1, SpanId1, TraceFlags1},
{TraceId2, SpanId2, TraceFlags2})
end).

0 comments on commit b9024d1

Please sign in to comment.