Skip to content
This repository has been archived by the owner on Oct 26, 2020. It is now read-only.

separate context propagation otep #4

Merged
merged 9 commits into from
Nov 20, 2019
36 changes: 36 additions & 0 deletions src/opentelemetry.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@
-module(opentelemetry).

-export([set_default_tracer/1,
set_default_context_manager/1,
get_context_manager/0,
get_tracer/0,
get_tracer/1,
set_http_extractor/1,
get_http_extractor/0,
set_http_injector/1,
get_http_injector/0,
timestamp/0,
links/1,
link/4,
Expand Down Expand Up @@ -106,12 +112,42 @@
set_default_tracer(Tracer) ->
persistent_term:put({?MODULE, default_tracer}, Tracer).

set_default_context_manager(ContextModule) ->
persistent_term:put({?MODULE, context_manager}, ContextModule).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to guard that this is actually a module (or an atom if we don't check for loading)? Like there's a risk of setting a bad value and insta-killing all the things, no?

In fact looking at the call below, the expected form is {Module, Args} and asserting there could prevent huge mistakes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agh yea, very good catch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it is actually used as just a module atom. Clearly need some typespecs so dialyzer will catch this shit where get_* is used.

I'm now unsure if there should be a tuple or not. Depends on if some state (that can't be updated because it is in a persistent term) is needed. I think I have an idea, will update this in a bit.


get_context_manager() ->
persistent_term:get({?MODULE, context_manager}, {ot_ctx_noop, []}).

get_tracer() ->
persistent_term:get({?MODULE, default_tracer}, {ot_tracer_noop, []}).

get_tracer(_Name) ->
persistent_term:get({?MODULE, default_tracer}, {ot_tracer_noop, []}).

set_http_extractor(List) ->
Fun = fun(Headers) ->
lists:foldl(fun(Extract, ok) ->
Extract(Headers),
ok
end, ok, List),
ok
end,
persistent_term:put({?MODULE, http_extractor}, Fun).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module is no longer safe for hot-code loading, since after two editions of this module being loaded, the Fun will need to be reaped, ending up killing all processes that hold a reference to it?

Though from a quick test, it seems like it just eventually turns into a badfun, so it's not that bad?

I guess the only workaround would be to extract the function to have a kind of "state" argument and use a fully qualified fun to make it work. Probably not worth the effort at this point in time?


set_http_injector(List) ->
Fun = fun(Headers) ->
lists:foldl(fun(Inject, HeadersAcc) ->
Inject(HeadersAcc)
end, Headers, List)
end,
persistent_term:put({?MODULE, http_injector}, Fun).

get_http_extractor() ->
persistent_term:get({?MODULE, http_extractor}, fun(Headers) -> Headers end).

get_http_injector() ->
persistent_term:get({?MODULE, http_injector}, fun(_Headers) -> ok end).

-spec timestamp() -> integer().
timestamp() ->
erlang:system_time(nanosecond).
Expand Down
73 changes: 73 additions & 0 deletions src/ot_baggage.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
%%%------------------------------------------------------------------------
%% 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_baggage).

-export([ctx_key/0,
set/2,
get/1,
remove/1,
clear/0,
get_http_propagators/0]).

-type key() :: string().
-type value() :: string().

-export_type([key/0,
value/0]).

-define(BAGGAGE_KEY, '$__ot_baggage_ctx_key').

ctx_key() ->
?BAGGAGE_KEY.

-spec set(key(), value()) -> ok.
set(Key, Value) ->
ot_ctx:set_value(?BAGGAGE_KEY, Key, Value).

-spec get(key()) -> value().
get(Key)->
ot_ctx:get_value(?BAGGAGE_KEY, Key).

-spec remove(key()) -> ok.
remove(Key) ->
ot_ctx:remove(?BAGGAGE_KEY, Key).

-spec clear() -> ok.
clear() ->
ot_ctx:clear(?BAGGAGE_KEY).

-spec get_http_propagators() -> {ot_propagation:http_extractor(), ot_propagation:http_injector()}.
get_http_propagators() ->
ToText = fun(_Headers, undefined) ->
[];
(_Headers, Baggage) ->
List = maps:fold(fun(Key, Value, Acc) ->
[[Key, "=", Value] | Acc]
end, [], Baggage),
lists:join(",", List)
tsloughter marked this conversation as resolved.
Show resolved Hide resolved
end,
FromText = fun(String, CurrentBaggage) ->
Pairs = string:lexemes(String, [","]),
lists:foldl(fun(Pair, Acc) ->
[Key, Pair] = string:split(Pair, "="),
Acc#{Key => {Pair, unlimited_propagation}}
end, CurrentBaggage, Pairs)
end,
Inject = ot_ctx:http_injector(?BAGGAGE_KEY, ToText),
Extract = ot_ctx:http_extractor(?BAGGAGE_KEY, FromText),
{Extract, Inject}.
59 changes: 59 additions & 0 deletions src/ot_correlations.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
%%%------------------------------------------------------------------------
%% 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_correlations).

-export([ctx_key/0,
set/3,
get_http_propagators/0]).

-type key() :: string().
-type value() :: string().
-type hop_limit() :: no_propagation | unlimited_propagation.

-export_type([key/0,
value/0]).

-define(CORRELATIONS_KEY, '$__ot_correlations_ctx_key').

ctx_key() ->
?CORRELATIONS_KEY.

-spec set(key(), value(), hop_limit()) -> ok.
set(Key, Value, HopLimit) ->
ot_ctx:set_value(?CORRELATIONS_KEY, Key, {Value, HopLimit}).

-spec get_http_propagators() -> {ot_propagation:http_extractor(), ot_propagation:http_injector()}.
get_http_propagators() ->
ToText = fun(_Headers, Correlations) ->
List = maps:fold(fun(Key, {Value, unlimited_propagation}, Acc) ->
[[Key, "=", Value] | Acc];
(_Key, {_Value, no_propagation}, Acc) ->
Acc
end, [], Correlations),
lists:join(",", List)
end,
FromText = fun(String, CurrentCorrelations) ->
Pairs = string:lexemes(String, [","]),
lists:foldl(fun(Pair, Acc) ->
[Key, Pair] = string:split(Pair, "="),
Acc#{Key => {Pair, unlimited_propagation}}
end, CurrentCorrelations, Pairs)
end,
Inject = ot_ctx:http_injector(?CORRELATIONS_KEY, ToText),
Extract = ot_ctx:http_extractor(?CORRELATIONS_KEY, FromText),
{Extract, Inject}.
144 changes: 144 additions & 0 deletions src/ot_ctx.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
%%%------------------------------------------------------------------------
%% 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_ctx).

-export([set_value/3,
get_value/2,
with_value/4,
with_value/5,
remove/2,
clear/1,
set_current/2,
get_current/1,
http_extractor/2,
http_injector/2,
http_extractor/3,
http_injector/3]).

-type ctx() :: map().
-type namespace() :: term().
-type key() :: term().
-type value() :: term().

-callback set_value(namespace(), key(), value()) -> ok.
-callback get_value(namespace(), key()) -> value() | undefined.
-callback remove(namespace(), key()) -> ok.
-callback clear(namespace()) -> ok.
-callback set_current(namespace(), ctx()) -> ok.
-callback get_current(namespace()) -> ctx().

-export_type([ctx/0,
key/0,
value/0]).

-spec set_value(namespace(), key(), value()) -> ok.
set_value(Namespace, Key, Value) ->
set_value(opentelemetry:get_context_manager(), Namespace, Key, Value).

-spec set_value(module(), namespace(), key(), value()) -> ok.
set_value(CtxModule, Namespace, Key, Value) ->
CtxModule:set_value(Namespace, Key, Value).

-spec get_value(namespace(), key()) -> value().
get_value(Namespace, Key) ->
get_value(opentelemetry:get_context_manager(), Namespace, Key).

-spec get_value(module(), namespace(), key()) -> value().
get_value(Module, Namespace, Key) ->
Module:get_value(Namespace, Key).

-spec with_value(namespace(), key(), value(), fun()) -> ok.
with_value(Namespace, Key, Value, Fun) ->
with_value(opentelemetry:get_context_manager(), Namespace, Key, Value, Fun).

-spec with_value(module(), namespace(), key(), value(), fun()) -> ok.
with_value(CtxModule, Namespace, Key, Value, Fun) ->
PrevCtx = get_current(CtxModule, Namespace),
CtxModule:set_current(PrevCtx, Namespace, PrevCtx#{Key => Value}),
try
Fun()
after
set_current(CtxModule, Namespace, PrevCtx)
end.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this usage implies that the current values for the context can't be global, and only process-global at best? Otherwise running two of these at the same time may break things, right? Might be worth pointing out in a comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, context is tied to a process. I can note that.


-spec remove(namespace(), key()) -> ok.
remove(Namespace, Key) ->
remove(opentelemetry:get_context_manager(), Namespace, Key).

-spec remove(module(), namespace(), key()) -> ok.
remove(CtxModule, Namespace, Key) ->
CtxModule:remove(Namespace, Key).

-spec clear(namespace()) -> ok.
clear(Namespace) ->
clear(opentelemetry:get_context_manager(), Namespace).

-spec clear(module(), namespace()) -> ok.
clear(CtxModule, Namespace) ->
CtxModule:clear(Namespace).

-spec set_current(namespace(), ctx()) -> ok.
set_current(Namespace, Ctx) ->
set_current(opentelemetry:get_context_manager(), Namespace, Ctx).

-spec set_current(module(), namespace(), ctx()) -> ok.
set_current(CtxModule, Namespace, Ctx) ->
CtxModule:set_current(Namespace, Ctx).

-spec get_current(namespace()) -> ctx().
get_current(Namespace) ->
get_current(opentelemetry:get_context_manager(), Namespace).

-spec get_current(module(), namespace()) -> ctx().
get_current(CtxModule, Namespace) ->
CtxModule:get_current(Namespace).

http_extractor(Namespace, FromText) ->
http_extractor_(opentelemetry:get_context_manager(), Namespace, FromText).

http_extractor_(CtxModule, Namespace, FromText) ->
fun(_Headers) ->
String = "", %% Headers
New = FromText(String, CtxModule:get_current(Namespace)),
CtxModule:set_current(Namespace, New)
end.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just a stub?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yea, I'll finish this.


http_extractor(Namespace, Key, FromText) ->
http_extractor_(opentelemetry:get_context_manager(), Namespace, Key, FromText).

http_extractor_(CtxModule, Namespace, Key, FromText) ->
fun(Headers) ->
New = FromText(Headers, CtxModule:get_value(Namespace, Key)),
CtxModule:set_value(Namespace, Key, New)
end.

http_injector(Namespace, ToText) ->
http_injector_(opentelemetry:get_context_manager(), Namespace, ToText).

http_injector_(CtxModule, Namespace, ToText) ->
fun(Headers) ->
Headers ++ ToText(Headers, CtxModule:get_current(Namespace))
end.

http_injector(Namespace, Key, ToText) ->
http_injector_(opentelemetry:get_context_manager(), Namespace, Key, ToText).

http_injector_(CtxModule, Namespace, Key, ToText) ->
fun(Headers) ->
Headers ++ ToText(Headers, CtxModule:get_value(Namespace, Key))
end.
51 changes: 51 additions & 0 deletions src/ot_ctx_noop.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
%%%------------------------------------------------------------------------
%% 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_ctx_noop).

-behaviour(ot_ctx).

-export([set_value/3,
get_value/2,
remove/2,
clear/1,
set_current/2,
get_current/1]).

-spec set_value(ot_ctx:namespace(), ot_ctx:key(), ot_ctx:value()) -> ok.
set_value(_Namespace, _Key, _Value) ->
ok.

-spec get_value(ot_ctx:namespace(), ot_ctx:key()) -> ot_ctx:value().
get_value(_Namespace, _Key) ->
undefined.

-spec remove(ot_ctx:namespace(), ot_ctx:key()) -> ok.
remove(_Namespace, _Key) ->
ok.

-spec clear(ot_ctx:namespace()) -> ok.
clear(_Namespace) ->
ok.

-spec set_current(ot_ctx:namespace(), ot_ctx:ctx()) -> ok.
set_current(_Namespace, _Ctx) ->
ok.

-spec get_current(ot_ctx:namespace()) -> ot_ctx:ctx().
get_current(_Namespace) ->
#{}.
Loading