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

W3C interop tests #27

Merged
merged 2 commits into from Dec 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -59,5 +59,22 @@ If an Elixir script is wanted for the benchmarks they could be run like:
$ ERL_LIBS=_build/bench/lib/ mix run --no-mix-exs samples/run.exs
```

## W3C Trace Context Interop Tests

Start the interop web server in a shell:

``` shell
$ rebar3 as interop shell

> w3c_trace_context_interop:run().
```

Then, clone the [W3C Trace Context repo](https://github.com/w3c/trace-context) and run the tests:

``` shell
$ cd test
$ python3 test.py http://127.0.0.1:5000/test
```

## Contributing

62 changes: 62 additions & 0 deletions interop/w3c_trace_context_interop.erl
@@ -0,0 +1,62 @@
%%%------------------------------------------------------------------------
%% 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(w3c_trace_context_interop).

-export([run/0,
do/1]).

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

run() ->
{ok, _Pid} = inets:start(httpd, [{port, 5000},
{server_root, "/tmp"},
{document_root, "/tmp"},
{server_name, "trace_context_interop"},
{bind_address, "localhost"},
{modules, [?MODULE]}]),
ok.

do(Req) ->
Body = Req#mod.entity_body,

%% inets gives headers in the reverse order they came in
Headers = headers_to_binary(lists:reverse(Req#mod.parsed_header)),
List = jsone:decode(list_to_binary(Body)),

lists:foreach(fun(#{<<"arguments">> := Arguments,
<<"url">> := Url}) ->
ot_propagation:http_extract(Headers),
otel:start_span(<<"interop-test">>),
InjectedHeaders = ot_propagation:http_inject([]),
httpc:request(post, {binary_to_list(Url),
headers_to_list(InjectedHeaders),
"application/json",
jsone:encode(Arguments)}, [], [{body_format, binary}]),
otel:end_span()
end, List),

{proceed, [{response, {200, "ok"}}]}.

%%

headers_to_binary(Headers) ->
[{list_to_binary(K), list_to_binary(V)} || {K, V} <- Headers].

headers_to_list(Headers) ->
[{binary_to_list(K), binary_to_list(iolist_to_binary(V))} || {K, V} <- Headers].
7 changes: 5 additions & 2 deletions rebar.config
@@ -1,7 +1,7 @@
{erl_opts, [debug_info]}.
{deps, [{wts, "~> 0.3"},
{opentelemetry_api, {git, "https://github.com/tsloughter/opentelemetry-erlang-api",
{branch, "fq-funs"}}}]}.
{opentelemetry_api, {git, "https://github.com/open-telemetry/opentelemetry-erlang-api",
{branch, "master"}}}]}.

{shell, [{apps, [opentelemetry]},
{config, "config/sys.config"}]}.
Expand All @@ -12,6 +12,9 @@
[{test, [{erl_opts, [nowarn_export_all]},
{ct_opts, [{ct_hooks, [cth_surefire]}]}]},

{interop, [{deps, [jsone]},
{extra_src_dirs, ["interop"]}]},

{bench, [{deps, [benchee]},
{extra_src_dirs, ["samples"]},
{plugins, [rebar_mix]},
Expand Down
4 changes: 2 additions & 2 deletions rebar.lock
@@ -1,7 +1,7 @@
{"1.1.0",
[{<<"opentelemetry_api">>,
{git,"https://github.com/tsloughter/opentelemetry-erlang-api",
{ref,"39664e7c180d58ca27d8485f5a05c5711473f1dc"}},
{git,"https://github.com/open-telemetry/opentelemetry-erlang-api",
{ref,"12cbbd265997aa2e8231f18de8d7574fe67a8f37"}},
0},
{<<"rfc3339">>,{pkg,<<"rfc3339">>,<<"0.9.0">>},1},
{<<"wts">>,{pkg,<<"wts">>,<<"0.3.0">>},0}]}.
Expand Down
6 changes: 3 additions & 3 deletions src/opentelemetry_app.erl
Expand Up @@ -27,13 +27,13 @@ start(_StartType, _StartArgs) ->

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

opentelemetry_sup:start_link(Opts).

Expand Down
94 changes: 66 additions & 28 deletions src/ot_propagation_http_w3c.erl
Expand Up @@ -24,14 +24,19 @@

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

-define(VERSION, "00").
-define(VERSION, <<"00">>).

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

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

-define(KEY_MP, element(2, re:compile("^[a-z0-9][a-z0-9_*/-]{0,255}$|^([a-z0-9_*/-]{1,241})(@[a-z0-9_*/-]{1,14})$"))).
-define(VALUE_MP, element(2, re:compile("^[ -~]{0,256}$"))).

-define(MAX_TRACESTATE_PAIRS, 32).

-spec inject(ot_propagation:http_headers(),
{opentelemetry:span_ctx(), opentelemetry:span_ctx() | undefined} | undefined)
-> ot_propagation:http_headers().
Expand Down Expand Up @@ -62,14 +67,20 @@ encode_tracestate(#span_ctx{tracestate=Entries}) ->

-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 ->
case lists:keytake(?HEADER_KEY, 1, Headers) of
{value, {_, Value}, RestHeaders} ->
case lists:keymember(?HEADER_KEY, 1, RestHeaders) of
true ->
%% duplicate traceparent header found
undefined;
SpanCtx ->
Tracestate = tracestate_from_headers(Headers),
SpanCtx#span_ctx{tracestate=Tracestate}
false ->
case decode(string:trim(Value)) of
undefined ->
undefined;
SpanCtx ->
Tracestate = tracestate_from_headers(Headers),
{SpanCtx#span_ctx{tracestate=Tracestate}, undefined}
end
end;
_ ->
undefined
Expand All @@ -90,44 +101,71 @@ combine_headers(Key, Headers) ->
lists:foldl(fun({K, V}, Acc) ->
case string:equal(K, Key) of
true ->
[V, $, | Acc];
[Acc, $, | V];
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] ->
case string:split(Pair, "=", all) of
[Key, Value] when Value =/= [] andalso Value =/= <<>> ->
{iolist_to_binary(Key), iolist_to_binary(Value)};
[Key] ->
{iolist_to_binary(Key), <<>>}
_ ->
undefined
end.

%% note: version ff (255) not allowed by spec
decode(TraceContext) when is_list(TraceContext) ->
decode(list_to_binary(TraceContext));
decode(<<?VERSION, "-", TraceId:32/binary, "-", SpanId:16/binary, _/binary>>)
decode(<<_:2/binary, "-", 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>>) ->
decode(<<Version:2/binary, "-", TraceId:32/binary, "-", SpanId:16/binary, "-", Opts:2/binary>>)
when Version >= ?VERSION andalso Version =/= <<"ff">> ->
to_span_ctx(Version, TraceId, SpanId, Opts);
%% future versions could have more after Opts, so allow for a trailing -
decode(<<Version:2/binary, "-", TraceId:32/binary, "-", SpanId:16/binary, "-", Opts:2/binary, "-", _/binary>>)
when Version > ?VERSION andalso Version =/= <<"ff">> ->
to_span_ctx(Version, TraceId, SpanId, Opts);
decode(_) ->
undefined.

to_span_ctx(Version, TraceId, SpanId, Opts) ->
try
%% verify version is hexadecimal
_ = binary_to_integer(Version, 16),
#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}
trace_flags=case Opts of <<"01">> -> 1; <<"00">> -> 0; _ -> error(badarg) end}
catch
%% to integer from base 16 string failed
error:badarg ->
undefined
ferd marked this conversation as resolved.
Show resolved Hide resolved
end;
decode(_) ->
end.

tracestate_decode(Value) ->
parse_pairs(string:lexemes(Value, [$,])).

parse_pairs(Pairs) when length(Pairs) =< ?MAX_TRACESTATE_PAIRS ->
parse_pairs(Pairs, []);
parse_pairs(_) ->
undefined.

parse_pairs([], Acc) ->
Acc;
parse_pairs([Pair | Rest], Acc) ->
case split(string:trim(Pair)) of
{K, V} ->
case re:run(K, ?KEY_MP) =/= nomatch
andalso not lists:keymember(K, 1, Acc)
andalso re:run(V, ?VALUE_MP) =/= nomatch
of
false ->
undefined;
true ->
parse_pairs(Rest, Acc ++ [{K, V}])
end;
undefined ->
undefined
end.
4 changes: 2 additions & 2 deletions src/ot_span_sweeper.erl
Expand Up @@ -33,6 +33,7 @@

-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include("ot_span_ets.hrl").
-include("ot_tracer.hrl").
-include_lib("kernel/include/logger.hrl").

-record(data, {interval :: integer() | infinity,
Expand Down Expand Up @@ -161,6 +162,5 @@ expired_match_spec(Time, Return) ->
end_span(Span=#span{tracestate=Tracestate}) ->
%% hack to not lose tracestate when ending without span ctx
Span1 = ot_span_utils:end_span(Span#span{tracestate=Tracestate}),
Tracer = opentelemetry:get_tracer(),
Processors = ot_tracer_default:on_end(Tracer),
{_, #tracer{on_end_processors=Processors}} = opentelemetry:get_tracer(),
Processors(Span1).
3 changes: 2 additions & 1 deletion src/ot_tracer.hrl
Expand Up @@ -7,6 +7,7 @@
module :: module(),
span_module :: module(),
ctx_module :: module(),
processors :: [{module(), term()}],
on_start_processors :: fun((opentelemetry:span()) -> opentelemetry:span()),
on_end_processors :: fun((opentelemetry:span()) -> boolean() | {error, term()}),
sampler :: ot_sampler:sampler(),
resource :: term() | undefined}).
41 changes: 20 additions & 21 deletions src/ot_tracer_default.erl
Expand Up @@ -23,7 +23,6 @@
with_span/2,
with_span/3,
end_span/1,
on_end/1,
current_span_ctx/1,
b3_propagators/0,
w3c_propagators/0]).
Expand All @@ -40,27 +39,28 @@
-spec start_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts())
-> opentelemetry:span_ctx().
start_span(Tracer, Name, Opts) when is_map_key(sampler, Opts) ->
case ot_ctx:get_value(?TRACER_KEY, ?SPAN_CTX) of
{SpanCtx, _}=Ctx ->
Processors = on_start(Tracer),
SpanCtx1 = ot_span_ets:start_span(Name, Opts#{parent => SpanCtx}, Processors),
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, {SpanCtx1, Ctx}),
SpanCtx1;
_ ->
Processors = on_start(Tracer),
SpanCtx = ot_span_ets:start_span(Name, Opts#{parent => undefined}, Processors),
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, {SpanCtx, undefined}),
SpanCtx
end;
start_span_(Tracer, Name, Opts);
start_span(Tracer={_, #tracer{sampler=Sampler}}, Name, Opts) ->
start_span(Tracer, Name, Opts#{sampler => Sampler}).


on_start({_, #tracer{processors=Processors}}) ->
fun(Span) -> [P:on_start(Span, Config) || {P, Config} <- Processors] end.

on_end({_, #tracer{processors=Processors}}) ->
fun(Span) -> [P:on_end(Span, Config) || {P, Config} <- Processors] end.
start_span_({_, #tracer{on_start_processors=Processors}}, Name, Opts) when is_map_key(parent, Opts) ->
case maps:get(parent, Opts) of
{ParentSpanCtx, _}=Ctx ->
SpanCtx1 = ot_span_ets:start_span(Name, Opts#{parent => ParentSpanCtx}, Processors),
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, {SpanCtx1, Ctx}),
SpanCtx1;
ParentSpanCtx ->
SpanCtx1 = ot_span_ets:start_span(Name, Opts, Processors),
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, {SpanCtx1, {ParentSpanCtx, undefined}}),
SpanCtx1
end;
start_span_(Tracer, Name, Opts) ->
case ot_ctx:get_value(?TRACER_KEY, ?SPAN_CTX) of
{_SpanCtx, _}=ParentCtx ->
start_span_(Tracer, Name, Opts#{parent => ParentCtx});
_ ->
start_span_(Tracer, Name, Opts#{parent => undefined})
end.

-spec with_span(opentelemetry:tracer(), opentelemetry:span_ctx()) -> ok.
with_span(_Tracer, SpanCtx) ->
Expand Down Expand Up @@ -112,9 +112,8 @@ w3c_propagators() ->
%% @end
%%--------------------------------------------------------------------
-spec end_span(opentelemetry:tracer()) -> ok.
end_span(Tracer) ->
end_span({_, #tracer{on_end_processors=Processors}}) ->
{SpanCtx, ParentCtx} = current_ctx(),
Processors = on_end(Tracer),
ot_span_ets:end_span(SpanCtx, Processors),
ot_ctx:set_value(?TRACER_KEY, ?SPAN_CTX, ParentCtx),
ok.
Expand Down
11 changes: 10 additions & 1 deletion src/ot_tracer_server.erl
Expand Up @@ -44,7 +44,8 @@ init(Opts) ->

Tracer = #tracer{module=ot_tracer_default,
sampler=SamplerFun,
processors=Processors,
on_start_processors=on_start(Processors),
on_end_processors=on_end(Processors),
span_module=ot_span_ets,
ctx_module=ot_ctx_pdict},
opentelemetry:set_default_tracer({ot_tracer_default, Tracer}),
Expand All @@ -60,3 +61,11 @@ handle_call(_Msg, _From, State) ->

handle_cast(_Msg, State) ->
{noreply, State}.

%%

on_start(Processors) ->
fun(Span) -> [P:on_start(Span, Config) || {P, Config} <- Processors] end.

on_end(Processors) ->
fun(Span) -> [P:on_end(Span, Config) || {P, Config} <- Processors] end.