%% @copyright 2017 Takeru Ohta %% %% @doc Process Dictionary version of {@link passage}. %% %% The functions in this module operate on the span %% which stored in the process dictionary of the calling process. %% %% === Examples === %% %% ``` %% %% Registers `tracer' %% Context = passage_span_context_null, %% Sampler = passage_sampler_all:new(), %% Reporter = passage_reporter_process:new(self(), span), %% ok = passage_tracer_registry:register(tracer, Context, Sampler, Reporter), %% %% %% Starts a root span %% ok = passage_pd:start_span(example_root, [{tracer, tracer}]), %% %% %% Starts a child span %% ok = passage_pd:start_span(example_child), %% %% %% Finishes spans %% passage_pd:finish_span(), % child %% passage_pd:finish_span(), % root %% %% %% Receives the finished spans %% receive {span, FinishedChildSpan} -> ok end, %% receive {span, FinishedRootSpan} -> ok end. %% ''' -module(passage_pd). -include("opentracing.hrl"). %%------------------------------------------------------------------------------ %% Exported API %%------------------------------------------------------------------------------ -export([start_span/1, start_span/2]). -export([finish_span/0, finish_span/1]). -export([with_span/2, with_span/3]). -export([with_parent_span/2]). -export([current_span/0]). -export([set_tracer/1]). -export([set_operation_name/1]). -export([set_tags/1]). -export([set_baggage_items/1]). -export([get_baggage_items/0]). -export([log/1, log/2]). -export_type([with_span_option/0, with_span_options/0]). %%------------------------------------------------------------------------------ %% Macros %%------------------------------------------------------------------------------ -define(ANCESTORS_KEY, passage_span_ancestors). %%------------------------------------------------------------------------------ %% Exported Types %%------------------------------------------------------------------------------ -type with_span_options() :: [with_span_option()]. %% Options for {@link with_span/3} -type with_span_option() :: {error_if_exception, boolean()} | passage:start_span_option(). %% %%------------------------------------------------------------------------------ %% Exported Functions %%------------------------------------------------------------------------------ %% @equiv start_span(OperationName, []) -spec start_span(passage:operation_name()) -> ok. start_span(OperationName) -> start_span(OperationName, []). %% @doc Starts a span. %% %% The started span will be pushed to the process dictionary of the calling process. %% %% If there is no sampled span references, the value of span will be `undefined'. -spec start_span(passage:operation_name(), passage:start_span_options()) -> ok. start_span(OperationName, Options) -> Ancestors = get_ancestors(), Options1 = case Ancestors of [] -> Options; [undefined | _] -> Options; [Parent | _] -> [Parent | Options] end, case passage:start_span(OperationName, Options1) of undefined -> put_ancestors([undefined | Ancestors]); Span -> put_ancestors([{child_of, Span} | Ancestors]) end. %% @equiv finish_span([]) -spec finish_span() -> ok. finish_span() -> finish_span([]). %% @doc Pops the current span from process dictionary and finishes the span. %% %% The finished span will be sent an external observer via %% the reporter associated with the tracer of the span. -spec finish_span(passage:finish_span_options()) -> ok. finish_span(Options) -> case pop_span() of undefined -> ok; {_, Span} -> passage:finish_span(Span, Options) end. %% @equiv with_span(OperationName, [], Fun) -spec with_span(passage:operation_name(), Fun) -> Result when Fun :: fun (() -> Result), Result :: term(). with_span(OperationName, Fun) -> with_span(OperationName, [], Fun). %% @doc Starts a span enclosing `Fun'. -spec with_span(passage:operation_name(), with_span_options(), Fun) -> Result when Fun :: fun (() -> Result), Result :: term(). with_span(OperationName, Options, Fun) -> ErrorIfException = proplists:get_value(error_if_exception, Options, true), case ErrorIfException of false -> try start_span(OperationName, Options), Fun() after finish_span() end; true -> try start_span(OperationName, Options), Fun() catch Class:Error -> Stack = erlang:get_stacktrace(), log(#{?LOG_FIELD_ERROR_KIND => Class, ?LOG_FIELD_MESSAGE => Error, ?LOG_FIELD_STACK => Stack}, [error]), erlang:raise(Class, Error, Stack) after finish_span() end end. %% @doc Saves `ParentSpan' in the current process dictionary then executes `Fun'. %% %% Before returning from the function, %% `ParentSpan' will be popped from the process dictionary. -spec with_parent_span(ParentSpan, Fun) -> Result when ParentSpan :: {passage:ref_type(), passage:maybe_span()}, Fun :: fun (() -> Result), Result :: term(). with_parent_span({_, undefined}, Fun) -> push_span(undefined), try Fun() after pop_span() end; with_parent_span(ParentSpan, Fun) -> push_span(ParentSpan), try Fun() after pop_span() end. %% @doc Returns the current span stored in the process dictionary of the calling process. -spec current_span() -> passage:maybe_span(). current_span() -> case get(?ANCESTORS_KEY) of [{_, Span} | _] -> Span; _ -> undefined end. %% @doc Sets the tracer of the current span to `Tracer'. %% %% At the finish of the span, the reporter of `Tracer' will be used to report it. %% %% This change affects all descendants of the span. -spec set_tracer(passage:tracer_id()) -> ok. set_tracer(Tracer) -> update_current_span( fun (Span) -> passage_span:set_tracer(Span, Tracer) end). %% @doc Sets the operation name of the current span to `OperationName'. -spec set_operation_name(passage:operation_name()) -> ok. set_operation_name(OperationName) -> update_current_span( fun (Span) -> passage_span:set_operation_name(Span, OperationName) end). %% @doc Sets the tags of the current span to `Tags'. %% %% Note that the existing tags which have different keys with `Tags' are preserved. -spec set_tags(Tags) -> ok when Tags :: passage:tags() | fun (() -> passage:tags()). set_tags(Tags) -> update_current_span(fun (Span) -> passage:set_tags(Span, Tags) end). %% @doc Sets the baggage items of the current span to `Items'. %% %% Note that the existing items which have different keys with `Items' are preserved. %% %% See also: Set a baggage item (The OpenTracing Semantic Specification) -spec set_baggage_items(Items) -> ok when Items :: passage:baggage_items() | fun (() -> passage:baggage_items()). set_baggage_items(Items) -> update_current_span(fun (Span) -> passage:set_baggage_items(Span, Items) end). %% @doc Returns the baggage items carried by the current span. -spec get_baggage_items() -> passage:baggage_items(). get_baggage_items() -> Span = current_span(), passage:get_baggage_items(Span). %% @equiv log(Fields, []) -spec log(Fields) -> ok when Fields :: passage:log_fields() | fun (() -> passage:log_fields()). log(Fields) -> log(Fields, []). %% @doc Logs the `Fields' to the current span. -spec log(Fields, passage:log_options()) -> ok when Fields :: passage:log_fields() | fun (() -> passage:log_fields()). log(Fields, Options) -> update_current_span(fun (Span) -> passage:log(Span, Fields, Options) end). %%------------------------------------------------------------------------------ %% Internal Functions %%------------------------------------------------------------------------------ -spec get_ancestors() -> [passage:ref() | undefined]. get_ancestors() -> case get(?ANCESTORS_KEY) of undefined -> []; Ancestors -> Ancestors end. -spec put_ancestors([passage:ref() | undefined]) -> ok. put_ancestors(Ancestors) -> put(?ANCESTORS_KEY, Ancestors), ok. -spec update_current_span(Fun) -> ok when Fun :: fun ((passage_span:span()) -> passage_span:span()). update_current_span(Fun) -> case get(?ANCESTORS_KEY) of undefined -> ok; [] -> ok; [undefined | _] -> ok; [{Type, Span0} | Spans] -> Span1 = Fun(Span0), put_ancestors([{Type, Span1} | Spans]) end. -spec push_span(passage:ref() | undefined) -> ok. push_span(Span) -> put_ancestors([Span | get_ancestors()]). -spec pop_span() -> passage:ref() | undefined. pop_span() -> case get(?ANCESTORS_KEY) of undefined -> undefined; [] -> undefined; [Span | Spans] -> put_ancestors(Spans), Span end.