Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions apps/opentelemetry_api/src/otel_propagator_baggage.erl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@

-define(BAGGAGE_HEADER, <<"baggage">>).

%% Limits recommended by the W3C Baggage specification and applied by the
%% other OpenTelemetry SDK implementations (Java SDK 1.62.0
%% `W3CBaggagePropagator`, Go `propagation/baggage.go`, .NET
%% `BaggagePropagator.cs`, C++ `baggage.h`).
-define(MAX_BAGGAGE_BYTES, 8192).
-define(MAX_BAGGAGE_ENTRIES, 180).

%% @private
fields(_) ->
[?BAGGAGE_HEADER].
Expand Down Expand Up @@ -83,14 +90,33 @@ extract(Ctx, Carrier, _CarrierKeysFun, CarrierGet, _Options) ->
undefined ->
Ctx;
String ->
Pairs = string:lexemes(String, [$,]),
DecodedBaggage =
lists:foldl(fun(Pair, Acc) ->
[Key, Value] = string:split(Pair, "="),
Acc#{decode_key(Key) => decode_value(Value)}

end, #{}, Pairs),
otel_baggage:set_to(Ctx, DecodedBaggage)
case header_size(String) of
Size when Size > ?MAX_BAGGAGE_BYTES ->
Ctx;
_ ->
Pairs = string:lexemes(String, [$,]),
DecodedBaggage = decode_pairs(Pairs, #{}, 0),
otel_baggage:set_to(Ctx, DecodedBaggage)
end
end.

header_size(String) when is_binary(String) ->
byte_size(String);
header_size(String) when is_list(String) ->
iolist_size(String);
header_size(_) ->
0.

decode_pairs([], Acc, _N) ->
Acc;
decode_pairs(_Pairs, Acc, N) when N >= ?MAX_BAGGAGE_ENTRIES ->
Acc;
decode_pairs([Pair | Rest], Acc, N) ->
case string:split(Pair, "=") of
[Key, Value] ->
decode_pairs(Rest, Acc#{decode_key(Key) => decode_value(Value)}, N + 1);
_ ->
decode_pairs(Rest, Acc, N)
end.

%%
Expand Down
105 changes: 105 additions & 0 deletions apps/opentelemetry_api/test/otel_propagator_baggage_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
-module(otel_propagator_baggage_SUITE).

-compile(export_all).

-include_lib("stdlib/include/assert.hrl").
-include_lib("common_test/include/ct.hrl").

-include("opentelemetry.hrl").

-define(BAGGAGE_HEADER, <<"baggage">>).
-define(MAX_BAGGAGE_BYTES, 8192).
-define(MAX_BAGGAGE_ENTRIES, 180).

all() ->
[extract_simple,
extract_drops_oversized_header,
extract_caps_entry_count,
extract_within_caps,
extract_skips_malformed_pair,
extract_missing_header].

init_per_suite(Config) ->
application:load(opentelemetry_api),
Config.

end_per_suite(_Config) ->
ok.

init_per_testcase(_TC, Config) ->
otel_ctx:clear(),
Config.

end_per_testcase(_TC, _Config) ->
otel_ctx:clear(),
ok.

extract_simple(_Config) ->
Header = <<"k1=v1,k2=v2">>,
Ctx = extract(Header),
Baggage = otel_baggage:get_all(Ctx),
?assertEqual(2, maps:size(Baggage)),
?assertEqual({<<"v1">>, []}, maps:get(<<"k1">>, Baggage)),
?assertEqual({<<"v2">>, []}, maps:get(<<"k2">>, Baggage)),
ok.

extract_drops_oversized_header(_Config) ->
Header = build_header(2000),
?assert(byte_size(Header) > ?MAX_BAGGAGE_BYTES),
Ctx = extract(Header),
?assertEqual(#{}, otel_baggage:get_all(Ctx)),
ok.

extract_caps_entry_count(_Config) ->
%% Build a header that stays under the byte cap but carries more entries
%% than `?MAX_BAGGAGE_ENTRIES`. Single-character keys/values keep each
%% pair small enough that 300 entries are well under 8 KiB.
Header = build_short_header(300),
?assert(byte_size(Header) =< ?MAX_BAGGAGE_BYTES),
Ctx = extract(Header),
Baggage = otel_baggage:get_all(Ctx),
?assertEqual(?MAX_BAGGAGE_ENTRIES, maps:size(Baggage)),
ok.

extract_within_caps(_Config) ->
Header = build_short_header(50),
Ctx = extract(Header),
Baggage = otel_baggage:get_all(Ctx),
?assertEqual(50, maps:size(Baggage)),
ok.

extract_skips_malformed_pair(_Config) ->
Header = <<"k1=v1,malformed,k2=v2">>,
Ctx = extract(Header),
Baggage = otel_baggage:get_all(Ctx),
?assertEqual(2, maps:size(Baggage)),
?assertEqual({<<"v1">>, []}, maps:get(<<"k1">>, Baggage)),
?assertEqual({<<"v2">>, []}, maps:get(<<"k2">>, Baggage)),
ok.

extract_missing_header(_Config) ->
Ctx0 = otel_ctx:new(),
Ctx1 = otel_propagator_baggage:extract(Ctx0, #{},
fun(C) -> maps:keys(C) end,
fun(K, C) -> maps:get(K, C, undefined) end,
[]),
?assertEqual(Ctx0, Ctx1),
ok.

%% helpers

extract(Header) ->
Carrier = #{?BAGGAGE_HEADER => Header},
Ctx = otel_ctx:new(),
otel_propagator_baggage:extract(Ctx, Carrier,
fun(C) -> maps:keys(C) end,
fun(K, C) -> maps:get(K, C, undefined) end,
[]).

build_header(N) ->
Pairs = [io_lib:format("k~B=v~B", [I, I]) || I <- lists:seq(1, N)],
iolist_to_binary(lists:join(<<",">>, Pairs)).

build_short_header(N) ->
Pairs = [["k", integer_to_list(I), "=v"] || I <- lists:seq(1, N)],
iolist_to_binary(lists:join(<<",">>, Pairs)).