| %% @copyright 2017 Takeru Ohta <phjgt308@gmail.com> |
| %% |
| %% @doc API for operating active spans. |
| %% |
| %% Most of the functions and types provided in this module are based on the |
| %% <a href="https://github.com/opentracing/specification/blob/1.1/specification.md">OpenTracing API</a>. |
| %% |
| %% === 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 |
| %% RootSpan = passage:start_span(example_root, [{tracer, tracer}]), |
| %% |
| %% %% Starts a child span |
| %% ChildSpan = passage:start_span(example_child, [{child_of, RootSpan}]), |
| %% |
| %% %% Finishes spans |
| %% passage:finish_span(ChildSpan), |
| %% passage:finish_span(RootSpan), |
| %% |
| %% %% Receives the finished spans |
| %% receive {span, FinishedChildSpan} -> ok end, |
| %% receive {span, FinishedRootSpan} -> ok end. |
| %% ''' |
| -module(passage). |
| |
| -include("opentracing.hrl"). |
| |
| %%------------------------------------------------------------------------------ |
| %% Exported API |
| %%------------------------------------------------------------------------------ |
| -export([start_span/1, start_span/2]). |
| -export([finish_span/1, finish_span/2]). |
| -export([set_operation_name/2]). |
| -export([set_tags/2]). |
| -export([set_baggage_items/2]). |
| -export([get_baggage_items/1]). |
| -export([log/2, log/3]). |
| -export([inject_span/4, extract_span/4]). |
| -export([strip_span/1]). |
| |
| -export_type([tracer_id/0]). |
| -export_type([maybe_span/0]). |
| -export_type([operation_name/0]). |
| -export_type([start_span_option/0, start_span_options/0]). |
| -export_type([finish_span_option/0, finish_span_options/0]). |
| -export_type([tags/0, tag_name/0, tag_value/0]). |
| -export_type([refs/0, ref/0, ref_type/0]). |
| -export_type([baggage_items/0, baggage_item_name/0, baggage_item_value/0]). |
| -export_type([log_fields/0, log_field_name/0, log_field_value/0]). |
| -export_type([log_option/0, log_options/0]). |
| |
| %%------------------------------------------------------------------------------ |
| %% Exported Types |
| %%------------------------------------------------------------------------------ |
| -type tracer_id() :: atom(). |
| %% Tracer identifier. |
| |
| -type maybe_span() :: passage_span:span() | undefined. |
| %% Sampled or unsampled span. |
| %% |
| %% `undefined' means the span is unsampled. |
| |
| -type operation_name() :: atom(). |
| %% Operation name. |
| |
| -type refs() :: [ref()]. |
| %% Span references. |
| |
| -type ref() :: {ref_type(), passage_span:span()}. |
| %% Span reference. |
| %% |
| %% Note that the values of tags, references and logs of a reference are set to empty |
| %% when the associated span is created. |
| %% |
| %% See also: <a href="https://github.com/opentracing/specification/blob/1.1/specification.md#references-between-spans">References between Spans (The OpenTracing Semantic Specification)</a> |
| |
| -type ref_type() :: child_of | follows_from. |
| %% Span reference type. |
| |
| -type tags() :: #{tag_name() => tag_value()}. |
| %% Span tags. |
| %% |
| %% See also: <a href="https://github.com/opentracing/specification/blob/1.1/semantic_conventions.md#span-tags-table">Standard span tags (OpenTracing API)</a> |
| |
| -type tag_name() :: atom(). |
| %% Tag name. |
| |
| -type tag_value() :: term(). |
| %% Tag value. |
| |
| -type baggage_items() :: #{baggage_item_name() => baggage_item_value()}. |
| %% Baggage items. |
| %% |
| %% Baggage items are just key/value pairs that cross OS process boundaries. |
| |
| -type baggage_item_name() :: binary(). |
| %% Baggage item name. |
| |
| -type baggage_item_value() :: binary(). |
| %% Baggage item value. |
| |
| -type log_fields() :: #{log_field_name() => log_field_value()}. |
| %% Span log fields. |
| %% |
| %% See also: <a href="https://github.com/opentracing/specification/blob/1.1/semantic_conventions.md#log-fields-table">Standard log fields (OpenTracing API)</a> |
| |
| -type log_field_name() :: atom(). |
| %% Log field name. |
| |
| -type log_field_value() :: term(). |
| %% Log field value. |
| |
| -type start_span_options() :: [start_span_option()]. |
| %% Options for {@link start_span/2}. |
| |
| -type start_span_option() :: {tracer, tracer_id()} |
| | {tags, tags()} |
| | {ref_type(), maybe_span()} |
| | {time, erlang:timestamp()}. |
| %% <ul> |
| %% <li><b>tracer</b>: The tracer used for tracing the span. If this option is omitted, the span will never be a root span. If the span has any valid references, this option will be ignored.</li> |
| %% <li><b>time</b>: Start timestamp of the span. The default value is `erlang:timestamp()'.</li> |
| %% <li><b>tags</b>: Tags associated to the span. The default value is `#{}'.</li> |
| %% <li><b>child_of|follows_from</b>: Specifies a references related to the span. This option can be presented more than once.</li> |
| %% </ul> |
| |
| -type finish_span_options() :: [finish_span_option()]. |
| %% Options for {@link finish_span/2}. |
| |
| -type finish_span_option() :: {time, erlang:timestamp()} |
| | {lifetime, pid()}. |
| %% <ul> |
| %% <li><b>time</b>: Finish timestamp of the span. The default value is `erlang:timestamp()'.</li> |
| %% <li><b>lifetime</b>: If this option is specified, the report of the finished span will be delayed until the lifetime process exits.</li> |
| %% </ul> |
| |
| -type log_options() :: [log_option()]. |
| %% Options for {@link log/3}. |
| |
| -type log_option() :: {time, erlang:timestamp()} |
| | error | {error, boolean()}. |
| %% <ul> |
| %% <li><b>time</b>: Timestamp of the log. The default value is `erlang:timestamp()'.</li> |
| %% <li><b>error</b>: |
| %% If this option presents, the log will be treated as an error log. |
| %% That is the `event' field with the value `error' will be added automatically. |
| %% In addition, the tag `#{error => true}' will be set to the calling span. |
| %% </li> |
| %% </ul> |
| |
| %%------------------------------------------------------------------------------ |
| %% Exported Functions |
| %%------------------------------------------------------------------------------ |
| %% @equiv start_span(OperationName, []) |
| -spec start_span(operation_name()) -> maybe_span(). |
| start_span(OperationName) -> |
| start_span(OperationName, []). |
| |
| %% @doc Starts a span. |
| %% |
| %% If any of the following conditions is matched, |
| %% a valid span object (i.e., non `undefined') will be returned. |
| %% |
| %% <ul> |
| %% <li>1. There are any valid (i.e., non `undefined') span references.</li> |
| %% <li>2. The `sampling.priority' tag exists and the value is a positive integer.</li> |
| %% <li>3. A `tracer' is specified and its sampler has determined to sample next span.</li> |
| %% </ul> |
| %% |
| %% If the first condition matches, |
| %% the tracer associated with the first reference will be used for tracing the resulting span. |
| -spec start_span(operation_name(), start_span_options()) -> maybe_span(). |
| start_span(OperationName, Options) -> |
| Result = |
| (fun Recur ([], Acc) -> Acc; |
| Recur ([{tracer, T} | L], error) -> Recur(L, {ok, T}); |
| Recur ([{_, undefined} | L], Acc) -> Recur(L, Acc); |
| Recur ([{child_of, _} | _], _) -> ignore; |
| Recur ([{follows_from, _} | _], _) -> ignore; |
| Recur ([_ | L], Acc) -> Recur(L, Acc) |
| end)(Options, error), |
| case Result of |
| error -> undefined; |
| ignore -> passage_span:start(OperationName, Options); |
| {ok, Tracer} -> passage_span:start_root(Tracer, OperationName, Options) |
| end. |
| |
| %% @equiv finish_span(Span, []) |
| -spec finish_span(maybe_span()) -> ok. |
| finish_span(Span) -> |
| finish_span(Span, []). |
| |
| %% @doc Finishes the span. |
| %% |
| %% The finished span will be sent an external observer via |
| %% the reporter associated with the tracer of the span. |
| %% |
| %% Note that if you call this function on the same span more than once, |
| %% duplicate reports will be sent. |
| -spec finish_span(maybe_span(), finish_span_options()) -> ok. |
| finish_span(undefined, _) -> ok; |
| finish_span(Span, Options) -> |
| case lists:keyfind(lifetime, 1, Options) of |
| false -> passage_span:finish(Span, Options); |
| {_, Pid} -> |
| spawn(fun () -> |
| Monitor = monitor(process, Pid), |
| finish_span_when_process_exits(Monitor, Span, Options) |
| end), |
| ok |
| end. |
| |
| %% @doc Sets the operation name of `Span' to `Name'. |
| -spec set_operation_name(maybe_span(), operation_name()) -> maybe_span(). |
| set_operation_name(undefined, _) -> undefined; |
| set_operation_name(Span, Name) -> passage_span:set_operation_name(Span, Name). |
| |
| %% @doc Sets the tags of `Span' to `Tags'. |
| %% |
| %% Note that the existing tags which have different keys with `Tags' are preserved. |
| -spec set_tags(maybe_span(), Tags) -> maybe_span() when |
| Tags :: tags() | fun (() -> tags()). |
| set_tags(undefined, _) -> undefined; |
| set_tags(Span, Tags = #{}) -> passage_span:set_tags(Span, Tags); |
| set_tags(Span, Fun) -> set_tags(Span, Fun()). |
| |
| %% @doc Sets the baggage items of `Span' to `Items'. |
| %% |
| %% Note that the existing items which have different keys with `Items' are preserved. |
| %% |
| %% See also: <a href="https://github.com/opentracing/specification/blob/1.1/specification.md#set-a-baggage-item">Set a baggage item (The OpenTracing Semantic Specification)</a> |
| -spec set_baggage_items(maybe_span(), Items) -> maybe_span() when |
| Items :: baggage_items() | fun (() -> baggage_items()). |
| set_baggage_items(undefined, _) -> undefined; |
| set_baggage_items(Span, Items = #{}) -> passage_span:set_baggage_items(Span, Items); |
| set_baggage_items(Span, Fun) -> set_baggage_items(Span, Fun()). |
| |
| %% @doc Returns the baggage items carried by `Span'. |
| -spec get_baggage_items(Span :: maybe_span()) -> baggage_items(). |
| get_baggage_items(undefined) -> #{}; |
| get_baggage_items(Span) -> passage_span:get_baggage_items(Span). |
| |
| %% @equiv log(Span, Fields, []) |
| -spec log(maybe_span(), Fields) -> maybe_span() when |
| Fields :: log_fields() | fun (() -> log_fields()). |
| log(Span, Fields) -> |
| log(Span, Fields, []). |
| |
| %% @doc Logs the `Fields' to `Span'. |
| -spec log(maybe_span(), Fields, log_options()) -> maybe_span() when |
| Fields :: log_fields() | fun (() -> log_fields()). |
| log(undefined, _, _) -> undefined; |
| log(Span, Fun, Options) when is_function(Fun) -> log(Span, Fun(), Options); |
| log(Span0, Fields0, Options) -> |
| case proplists:get_value(error, Options, false) of |
| false -> passage_span:log(Span0, Fields0, Options); |
| true -> |
| Fields1 = maps:merge(Fields0, #{?LOG_FIELD_EVENT => error}), |
| Span1 = passage_span:log(Span0, Fields1, Options), |
| passage_span:set_tags(Span1, #{?TAG_ERROR => true}) |
| end. |
| |
| %% @doc Injects `Span' into `Carrier'. |
| -spec inject_span(Span, Format, InjectFun, Carrier) -> Carrier when |
| Span :: maybe_span(), |
| Format :: passage_span_context:format(), |
| InjectFun :: passage_span_context:inject_fun(), |
| Carrier :: passage_span_context:carrier(). |
| inject_span(undefined, _, _, Carrier) -> Carrier; |
| inject_span(Span, Format, InjectFun, Carrier) -> |
| Context = passage_span:get_context(Span), |
| Tracer = passage_span:get_tracer(Span), |
| case passage_tracer_registry:get_span_context_module(Tracer) of |
| error -> Carrier; |
| {ok, Module} -> Module:inject_span_context(Context, Format, InjectFun, Carrier) |
| end. |
| |
| %% @doc Extracts a span from `Carrier'. |
| %% |
| %% If `Carrier' has no span context, this function will return `undefined'. |
| -spec extract_span(Tracer, Format, IterateFun, Carrier) -> maybe_span() when |
| Tracer :: tracer_id(), |
| Format :: passage_span_context:format(), |
| IterateFun :: passage_span_context:iterate_fun(), |
| Carrier :: passage_span_context:carrier(). |
| extract_span(Tracer, Format, IterateFun, Carrier) -> |
| case passage_tracer_registry:get_span_context_module(Tracer) of |
| error -> undefined; |
| {ok, Module} -> |
| case Module:extract_span_context(Format, IterateFun, Carrier) of |
| error -> undefined; |
| {ok, Context} -> passage_span:make_extracted_span(Tracer, Context) |
| end |
| end. |
| |
| %% @doc Strips the tags, logs and references from the given span. |
| -spec strip_span(maybe_span()) -> maybe_span(). |
| strip_span(undefined) -> undefined; |
| strip_span(Span) -> passage_span:strip(Span). |
| |
| %%------------------------------------------------------------------------------ |
| %% Interal Functions |
| %%------------------------------------------------------------------------------ |
| -spec finish_span_when_process_exits( |
| reference(), passage_span:span(), finish_span_options()) -> ok. |
| finish_span_when_process_exits(Monitor, Span0, Options) -> |
| receive |
| {'DOWN', Monitor, _, _, Reason} -> |
| IsError = |
| case Reason of |
| normal -> false; |
| shutdown -> false; |
| {shutdown, _} -> false; |
| _ -> true |
| end, |
| Span1 = log(Span0, #{?LOG_FIELD_EVENT => exit, 'exit.reason' => Reason}), |
| Span2 = |
| case IsError of |
| false -> Span1; |
| true -> set_tags(Span1, #{?TAG_ERROR => true}) |
| end, |
| passage_span:finish(Span2, Options); |
| _ -> |
| finish_span_when_process_exits(Monitor, Span0, Options) |
| end. |