Current section

Files

Jump to
opentelemetry_api src otel_tracestate.erl
Raw

src/otel_tracestate.erl

%%%------------------------------------------------------------------------
%% 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 tracestate provides additional vendor-specific trace identification
%% information across different distributed tracing systems. It represents an
%% immutable list consisting of key/value pairs, each pair is referred to as a
%% list-member.
%%
%% Keys and values are strings of up to 256 printable US-ASCII characters,
%% conforming to the W3C spec https://www.w3.org/TR/trace-context/#tracestate-field
%%
%% @end
%%%-----------------------------------------------------------------------
-module(otel_tracestate).
-export([new/0,
new/1,
get/2,
add/3,
remove/2,
update/3,
decode_header/1,
encode_header/1
]).
-type members() :: [{unicode:latin1_chardata(), unicode:latin1_chardata()}].
-record(tracestate, {members :: members()}).
-type t() :: #tracestate{}.
-export_type([t/0,
members/0]).
%% See https://www.w3.org/TR/trace-context/#tracestate-header
%% for the limits and string requirements that make up the regexes
-define(MAX_MEMBERS, 32).
%%
-define(KEY_MP, persistent_term:get({otel_tracestate, key_mp})).
-define(VALUE_MP, persistent_term:get({otel_tracestate, value_mp})).
-define(IS_STRING, (is_atom(Key) orelse is_string(Key) orelse is_binary(Key))).
-spec new() -> t().
new() ->
%% api had no setup so we have to ensure the persistent term for tracestate
%% regexes are setup each time `new' is called
ensure_regex_pterms(),
#tracestate{members=[]}.
-spec new([{unicode:latin1_chardata(), unicode:latin1_chardata()}]) -> t().
new(List) ->
ensure_regex_pterms(),
Members = [Element || {Key, Value}=Element <- List, is_valid(Key, Value)],
#tracestate{members=Members}.
-spec add(unicode:latin1_chardata(), unicode:latin1_chardata(), t()) -> t().
add(Key, Value, Tracestate=#tracestate{members=TracestateList}) ->
case is_valid(Key, Value) of
true ->
Tracestate#tracestate{members=[{Key, Value} | TracestateList]};
false ->
Tracestate
end.
-spec get(unicode:latin1_chardata(), t()) -> unicode:latin1_chardata().
get(Key, #tracestate{members=TracestateList}) ->
case lists:keyfind(Key, 1, TracestateList) of
false ->
"";
{_, Value} ->
Value
end.
-spec remove(unicode:latin1_chardata(), t()) -> t().
remove(Key, Tracestate=#tracestate{members=TracestateList}) ->
Tracestate#tracestate{members=lists:keydelete(Key, 1, TracestateList)}.
-spec update(unicode:latin1_chardata(), unicode:latin1_chardata(), t()) -> t().
update(Key, Value, Tracestate=#tracestate{members=TracestateList}) ->
case is_valid(Key, Value) of
true ->
Tracestate#tracestate{members=[{Key, Value} | lists:keydelete(Key, 1, TracestateList)]};
false ->
Tracestate
end.
-spec decode_header(unicode:latin1_binary() | undefined) -> t().
decode_header(undefined) ->
new();
decode_header(Value) ->
ensure_regex_pterms(),
#tracestate{members=parse_pairs(string:lexemes(Value, [$,]))}.
-spec encode_header(t()) -> unicode:latin1_binary().
encode_header(#tracestate{members=Entries=[_|_]}) ->
StateHeaderValue = lists:join($,, [[Key, $=, Value] || {Key, Value} <- Entries]),
otel_utils:assert_to_binary(StateHeaderValue);
encode_header(_) ->
<<>>.
%%
ensure_regex_pterms() ->
try
_ = ?KEY_MP
catch
error:badarg ->
{ok, KeyMP} = re:compile("^(([a-z][_0-9a-z\-\*\/]{0,255})|([a-z0-9][_0-9a-z-*/]{0,240}@[a-z][_0-9a-z-*/]{0,13}))$"),
{ok, ValueMP} = re:compile("^([ -+--<>-~]{0,255}[!-+--<>-~])$"),
persistent_term:put({otel_tracestate, key_mp}, KeyMP),
persistent_term:put({otel_tracestate, value_mp}, ValueMP)
end.
is_valid(Key, Value) ->
try
re:run(Key, ?KEY_MP, [{capture, none}]) =:= match
andalso re:run(Value, ?VALUE_MP, [{capture, none}]) =:= match
catch
_:_ ->
false
end.
parse_pairs(Pairs) when length(Pairs) =< ?MAX_MEMBERS ->
parse_pairs(Pairs, []);
parse_pairs(_) ->
[].
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 re:run(V, ?VALUE_MP) =/= nomatch
of
false ->
[];
true ->
%% replace existing key value or append to the end of the list
parse_pairs(Rest, lists:keystore(K, 1, Acc, {K, V}))
end;
undefined ->
[]
end.
split(Pair) ->
case string:split(Pair, "=", all) of
[Key, Value] when Value =/= [] andalso Value =/= <<>> ->
{otel_utils:assert_to_binary(Key),
otel_utils:assert_to_binary(Value)};
_ ->
undefined
end.