blob: 0f732db4b1a18b80f03ed94f259ba3cb691de056 [file] [log] [blame]
%% @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.