Skip to content
This repository was archived by the owner on Jul 31, 2023. It is now read-only.

Commit b3ab781

Browse files
authored
Tracestate and b3 headers (#142)
* add b3 headers and change span ctx header api to to/from_headers * remove deprecated hex maintainers entry from .app.src (#140) * support tracestate in context and headers * move pid reporter to src so it can be used in other tests * rename propagation modules to prefix with oc_propagation_
1 parent 2006516 commit b3ab781

15 files changed

+546
-107
lines changed

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,12 @@ More details on working with spans can be found [here](span.md) and in the modul
5656

5757
#### <a name="Propagating_Span_Context">Propagating Span Context</a> ####
5858

59-
Opencensus comes with two forms of span context encoding for sending over the wire. `oc_span_ctx_header` encodes a span context suitable for transfering as an HTTP header and `oc_span_ctx_binary` will encode and decode a binary form used in GRPC and other binary protocols.
59+
Builtin support for encoding and decoding span context from HTTP headers comes in two formats:
6060

61-
For example, creating the header for sending with an HTTP client might look like:
61+
* [W3C Trace Context](https://www.w3.org/TR/trace-context/)
62+
* [B3](https://github.com/openzipkin/b3-propagation)
6263

63-
```erlang
64-
EncodedSpanCtx = oc_span_ctx_header:encode(ocp:current_span_ctx()),
65-
Headers = [{oc_span_ctx_header:field_name(), EncodedSpanCtx}],
66-
```
64+
Additionally `oc_propagation_binary` will encode and decode a binary form used for GRPC and other binary protocols.
6765

6866
#### <a name="Samplers">Samplers</a> ####
6967

include/opencensus.hrl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,17 @@
3838
%% 64 bit int span id
3939
span_id :: opencensus:span_id() | undefined,
4040
%% 8-bit integer, lowest bit is if it is sampled
41-
trace_options = 1 :: integer() | undefined
41+
trace_options = 1 :: integer() | undefined,
42+
%% Tracestate represents tracing-system specific context in a list of key-value pairs.
43+
%% Tracestate allows different vendors propagate additional information and
44+
%% inter-operate with their legacy Id formats.
45+
tracestate :: opencensus:tracestate() | undefined
4246
}).
4347

48+
-record(tracestate, {
49+
entries :: [{unicode:latin1_chardata(), unicode:latin1_chardata()}]
50+
}).
51+
4452
-record(span, {
4553
%% name of the span
4654
name :: unicode:unicode_binary(),
@@ -53,6 +61,8 @@
5361
%% 64 bit int parent span
5462
parent_span_id :: opencensus:span_id() | undefined,
5563

64+
tracestate :: opencensus:tracestate() | undefined,
65+
5666
%% 8-bit integer, lowest bit is if it is sampled
5767
trace_options = 1 :: integer() | undefined,
5868

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
%% [https://github.com/census-instrumentation/opencensus-specs/blob/7b426409/encodings/BinaryEncoding.md]
1818
%% @end
1919
%%%-------------------------------------------------------------------------
20-
-module(oc_span_ctx_binary).
20+
-module(oc_propagation_binary).
2121

2222
-export([encode/1,
2323
decode/1]).

src/oc_propagation_http_b3.erl

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
%%%-------------------------------------------------------------------------
2+
%% Copyright 2017, OpenCensus Authors
3+
%% Licensed under the Apache License, Version 2.0 (the "License");
4+
%% you may not use this file except in compliance with the License.
5+
%% You may obtain a copy of the License at
6+
%%
7+
%% http://www.apache.org/licenses/LICENSE-2.0
8+
%%
9+
%% Unless required by applicable law or agreed to in writing, software
10+
%% distributed under the License is distributed on an "AS IS" BASIS,
11+
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
%% See the License for the specific language governing permissions and
13+
%% limitations under the License.
14+
%%
15+
%% @doc Functions to support the http header format of the tracecontext spec
16+
%% Implements the spec found here https://github.com/openzipkin/b3-propagation
17+
%% @end
18+
%%%-------------------------------------------------------------------------
19+
-module(oc_propagation_http_b3).
20+
21+
-export([to_headers/1,
22+
from_headers/1]).
23+
24+
-include("opencensus.hrl").
25+
26+
-define(B3_TRACE_ID, <<"X-B3-TraceId">>).
27+
-define(B3_SPAN_ID, <<"X-B3-SpanId">>).
28+
-define(B3_SAMPLED, <<"X-B3-Sampled">>).
29+
30+
-define(IS_SAMPLED(S), S =:= "1" orelse S =:= <<"1">> orelse S =:= "true" orelse S =:= <<"true">>).
31+
32+
-spec to_headers(opencensus:span_ctx()) -> maybe(list()).
33+
to_headers(#span_ctx{trace_id=TraceId,
34+
span_id=SpanId}) when TraceId =:= 0
35+
; SpanId =:= 0 ->
36+
[];
37+
to_headers(#span_ctx{trace_id=TraceId,
38+
span_id=SpanId,
39+
trace_options=TraceOptions}) ->
40+
Options = case TraceOptions band 1 of 1 -> "1"; _ -> "0" end,
41+
EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]),
42+
EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]),
43+
[{?B3_TRACE_ID, EncodedTraceId},
44+
{?B3_SPAN_ID, EncodedSpanId},
45+
{?B3_SAMPLED, Options}];
46+
to_headers(undefined) ->
47+
[].
48+
49+
-spec from_headers(list() | map()) -> maybe(opencensus:span_ctx()).
50+
from_headers(Headers) when is_map(Headers) ->
51+
from_headers(maps:to_list(Headers));
52+
from_headers(Headers) when is_list(Headers) ->
53+
try
54+
TraceId = trace_id(Headers),
55+
SpanId = span_id(Headers),
56+
Sampled = lookup(?B3_SAMPLED, Headers),
57+
#span_ctx{trace_id=string_to_integer(TraceId, 16),
58+
span_id=string_to_integer(SpanId, 16),
59+
trace_options=case Sampled of True when ?IS_SAMPLED(True) -> 1; _ -> 0 end}
60+
catch
61+
throw:invalid ->
62+
undefined;
63+
64+
%% thrown if _to_integer fails
65+
error:badarg ->
66+
undefined
67+
end;
68+
from_headers(_) ->
69+
undefined.
70+
71+
trace_id(Headers) ->
72+
case lookup(?B3_TRACE_ID, Headers) of
73+
TraceId when is_list(TraceId) orelse is_binary(TraceId) ->
74+
case string:length(TraceId) =:= 32 orelse string:length(TraceId) =:= 16 of
75+
true ->
76+
TraceId;
77+
_ ->
78+
throw(invalid)
79+
end;
80+
_ ->
81+
throw(invalid)
82+
end.
83+
84+
span_id(Headers) ->
85+
case lookup(?B3_SPAN_ID, Headers) of
86+
SpanId when is_list(SpanId) orelse is_binary(SpanId) ->
87+
case string:length(SpanId) =:= 32 orelse string:length(SpanId) =:= 16 of
88+
true ->
89+
SpanId;
90+
_ ->
91+
throw(invalid)
92+
end;
93+
_ ->
94+
throw(invalid)
95+
end.
96+
97+
%% find a header in a list, ignoring case
98+
lookup(_, []) ->
99+
undefined;
100+
lookup(Header, [{H, Value} | Rest]) ->
101+
case string:equal(Header, H, true, none) of
102+
true ->
103+
Value;
104+
false ->
105+
lookup(Header, Rest)
106+
end.
107+
108+
string_to_integer(S, Base) when is_binary(S) ->
109+
binary_to_integer(S, Base);
110+
string_to_integer(S, Base) when is_list(S) ->
111+
list_to_integer(S, Base).
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
%%%-------------------------------------------------------------------------
2+
%% Copyright 2017, OpenCensus Authors
3+
%% Licensed under the Apache License, Version 2.0 (the "License");
4+
%% you may not use this file except in compliance with the License.
5+
%% You may obtain a copy of the License at
6+
%%
7+
%% http://www.apache.org/licenses/LICENSE-2.0
8+
%%
9+
%% Unless required by applicable law or agreed to in writing, software
10+
%% distributed under the License is distributed on an "AS IS" BASIS,
11+
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
%% See the License for the specific language governing permissions and
13+
%% limitations under the License.
14+
%%
15+
%% @doc Functions to support the http header format of the tracecontext spec
16+
%% Implements the spec found here https://www.w3.org/TR/trace-context/
17+
%% @end
18+
%%%-------------------------------------------------------------------------
19+
-module(oc_propagation_http_tracecontext).
20+
21+
-export([to_headers/1,
22+
encode/1,
23+
from_headers/1,
24+
decode/1]).
25+
26+
-include("opencensus.hrl").
27+
28+
-define(VERSION, "00").
29+
30+
-define(ZERO_TRACEID, <<"00000000000000000000000000000000">>).
31+
-define(ZERO_SPANID, <<"0000000000000000">>).
32+
33+
-define(HEADER_KEY, <<"traceparent">>).
34+
-define(STATE_HEADER_KEY, <<"tracestate">>).
35+
36+
-spec to_headers(opencensus:span_ctx() | undefined) -> [{binary(), iolist()}].
37+
to_headers(#span_ctx{trace_id=TraceId,
38+
span_id=SpanId})
39+
when TraceId =:= 0 orelse SpanId =:= 0 ->
40+
[];
41+
to_headers(SpanCtx=#span_ctx{}) ->
42+
EncodedValue = encode(SpanCtx),
43+
[{?HEADER_KEY, EncodedValue} | encode_tracestate(SpanCtx)];
44+
to_headers(undefined) ->
45+
[].
46+
47+
-spec encode(opencensus:span_ctx()) -> iolist().
48+
encode(#span_ctx{trace_id=TraceId,
49+
span_id=SpanId,
50+
trace_options=TraceOptions}) ->
51+
Options = case TraceOptions band 1 of 1 -> <<"01">>; _ -> <<"00">> end,
52+
EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]),
53+
EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]),
54+
[?VERSION, "-", EncodedTraceId, "-", EncodedSpanId, "-", Options].
55+
56+
encode_tracestate(#span_ctx{tracestate=undefined}) ->
57+
[];
58+
encode_tracestate(#span_ctx{tracestate=#tracestate{entries=Entries}}) ->
59+
StateHeaderValue = lists:join($,, [[Key, $=, Value] || {Key, Value} <- Entries]),
60+
[{?STATE_HEADER_KEY, StateHeaderValue}].
61+
62+
-spec from_headers(list() | map()) -> maybe(opencensus:span_ctx()).
63+
from_headers(Headers) when is_map(Headers) ->
64+
decode(maps:get(?HEADER_KEY, Headers, undefined));
65+
from_headers(Headers) when is_list(Headers) ->
66+
case lists:keyfind(?HEADER_KEY, 1, Headers) of
67+
{_, Value} ->
68+
case decode(Value) of
69+
undefined ->
70+
undefined;
71+
SpanCtx ->
72+
Tracestate = tracestate_from_headers(Headers),
73+
SpanCtx#span_ctx{tracestate=Tracestate}
74+
end;
75+
_ ->
76+
undefined
77+
end.
78+
79+
tracestate_from_headers(Headers) ->
80+
%% could be multiple tracestate headers. Combine them all with comma separators
81+
case combine_headers(?STATE_HEADER_KEY, Headers) of
82+
[] ->
83+
undefined;
84+
FieldValue ->
85+
tracestate_decode(FieldValue)
86+
end.
87+
88+
combine_headers(Key, Headers) ->
89+
lists:foldl(fun({K, V}, Acc) ->
90+
case string:equal(K, Key) of
91+
true ->
92+
[V, $, | Acc];
93+
false ->
94+
Acc
95+
end
96+
end, [], Headers).
97+
98+
tracestate_decode(Value) ->
99+
%% TODO: the 512 byte limit should not include optional white space that can
100+
%% appear between list members.
101+
case iolist_size(Value) of
102+
Size when Size =< 512 ->
103+
#tracestate{entries=[split(Pair) || Pair <- string:lexemes(Value, [$,])]};
104+
_ ->
105+
undefined
106+
end.
107+
108+
split(Pair) ->
109+
case string:split(Pair, "=") of
110+
[Key, Value] ->
111+
{iolist_to_binary(Key), iolist_to_binary(Value)};
112+
[Key] ->
113+
{iolist_to_binary(Key), <<>>}
114+
end.
115+
116+
decode(TraceContext) when is_list(TraceContext) ->
117+
decode(list_to_binary(TraceContext));
118+
decode(<<?VERSION, "-", TraceId:32/binary, "-", SpanId:16/binary, _/binary>>)
119+
when TraceId =:= ?ZERO_TRACEID orelse SpanId =:= ?ZERO_SPANID ->
120+
undefined;
121+
decode(<<?VERSION, "-", TraceId:32/binary, "-", SpanId:16/binary, "-", Opts:2/binary, _/binary>>) ->
122+
try
123+
#span_ctx{trace_id=binary_to_integer(TraceId, 16),
124+
span_id=binary_to_integer(SpanId, 16),
125+
trace_options=case Opts of <<"01">> -> 1; _ -> 0 end}
126+
catch
127+
%% to integer from base 16 string failed
128+
error:badarg ->
129+
undefined
130+
end;
131+
decode(_) ->
132+
undefined.

src/oc_span.erl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
%%%-------------------------------------------------------------------------
1818
-module(oc_span).
1919

20-
-export([finish_span/1,
20+
-export([finish_span/2,
2121

2222
put_attribute/3,
2323
put_attributes/2,
@@ -41,12 +41,14 @@
4141
%% Finish a span, setting the end_time and sending to the reporter.
4242
%% @end
4343
%%--------------------------------------------------------------------
44-
-spec finish_span(maybe(opencensus:span())) -> true.
45-
finish_span(Span=#span{}) ->
44+
-spec finish_span(opencensus:span_ctx(), maybe(opencensus:span())) -> true.
45+
finish_span(#span_ctx{tracestate=Tracestate}, Span=#span{}) ->
4646
EndTime = wts:timestamp(),
47-
Span1 = Span#span{end_time=EndTime},
47+
%% update tracestate to what the context has when finished
48+
Span1 = Span#span{end_time=EndTime,
49+
tracestate=Tracestate},
4850
oc_reporter:store_span(Span1);
49-
finish_span(_) ->
51+
finish_span(_, _) ->
5052
true.
5153

5254
%%--------------------------------------------------------------------

src/oc_span_ctx_header.erl

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)