blob: 5521901fd60293f3998792a316199e33bedae841 [file] [log] [blame]
% 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.
-module(ctrace).
-vsn(1).
-export([
is_enabled/0,
with_span/2,
with_span/3,
start_span/1,
start_span/2,
finish_span/0,
finish_span/1,
has_span/0,
external_span/3,
tag/1,
log/1,
tags/0,
refs/0,
operation_name/0,
trace_id/0,
span_id/0,
tracer/0,
context/0,
match/2
]).
-include_lib("couch/include/couch_db.hrl").
-include_lib("passage/include/opentracing.hrl").
-include("ctrace.hrl").
-type operation()
:: atom()
| fun().
-type tags()
:: #{atom() => term()}.
-type log_fields()
:: #{atom() => term()}.
-type start_span_options()
:: [start_span_option()].
-type start_span_option()
:: {time, erlang:timespan()}
| {tags, tags()}.
-type finish_span_options()
:: [finish_span_option()].
-type finish_span_option()
:: {time, erlang:timespan()}.
-spec is_enabled() -> boolean().
is_enabled() ->
case get(?IS_ENABLED_KEY) of
undefined ->
Result = ctrace_config:is_enabled(),
put(?IS_ENABLED_KEY, Result),
Result;
IsEnabled ->
IsEnabled
end.
%% @equiv with_span(Operation, [], Fun)
-spec with_span(
Operation :: operation(),
Fun
) -> Result when
Fun :: fun (() -> Result),
Result :: term().
with_span(Operation, Fun) ->
with_span(Operation, #{}, Fun).
-spec with_span(
Operation :: operation(),
TagsOrOptions :: tags() | start_span_options(),
Fun
) -> Result when
Fun :: fun (() -> Result),
Result :: term().
with_span(Operation, ExtraTags, Fun) when is_map(ExtraTags) ->
with_span(Operation, [{tags, ExtraTags}], Fun);
with_span(Operation, Options, Fun) ->
try
start_span(Operation, Options),
Fun()
catch Type:Reason ->
Stack = erlang:get_stacktrace(),
log(#{
?LOG_FIELD_ERROR_KIND => Type,
?LOG_FIELD_MESSAGE => Reason,
?LOG_FIELD_STACK => Stack
}, [error]),
erlang:raise(Type, Reason, Stack)
after
finish_span()
end.
-spec start_span(
Operation :: operation()
) -> ok.
start_span(Operation) ->
start_span(Operation, []).
-spec start_span(
Operation :: operation(),
Options :: start_span_options()
) -> ok.
start_span(Operation, Options) ->
case is_enabled() of
true ->
do_start_span(Operation, Options);
false ->
ok
end.
do_start_span(Fun, Options) when is_function(Fun) ->
start_span(fun_to_op(Fun), Options);
do_start_span(OperationName, Options0) ->
Options1 = add_time(Options0),
case passage_pd:current_span() of
undefined ->
put(?ORIGIN_KEY, atom_to_binary(OperationName, utf8)),
Tags = case lists:keyfind(tags, 1, Options0) of
{tags, T} ->
T;
false ->
#{}
end,
case match(OperationName, Tags) of
true ->
Options = [
{tracer, ?MAIN_TRACER}
| maybe_start_root(Options1)
],
passage_pd:start_span(OperationName, Options);
false ->
ok
end;
Span ->
Options = add_tags([{child_of, Span} | Options1], #{
origin => get(?ORIGIN_KEY)
}),
passage_pd:start_span(OperationName, Options)
end.
-spec finish_span() -> ok.
finish_span() ->
finish_span([]).
-spec finish_span(
Options :: finish_span_options()
) -> ok.
finish_span(Options0) ->
Options = add_time(Options0),
passage_pd:finish_span(Options).
-spec tag(
Tags :: tags()
) -> ok.
tag(Tags) ->
passage_pd:set_tags(Tags).
-spec log(
Fields :: log_fields() | fun (() -> log_fields())
) -> ok.
log(FieldsOrFun) ->
log(FieldsOrFun, []).
log(FieldsOrFun, Options) ->
passage_pd:log(FieldsOrFun, Options).
-spec tags() -> tags().
tags() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
passage_span:get_tags(Span)
end.
-spec refs() -> passage:refs().
refs() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
passage_span:get_refs(Span)
end.
-spec has_span() -> boolean().
has_span() ->
passage_pd:current_span() =/= undefined.
-spec operation_name() -> atom().
operation_name() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
passage_span:get_operation_name(Span)
end.
-spec trace_id() -> 0..16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.
trace_id() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
Context = passage_span:get_context(Span),
jaeger_passage_span_context:get_trace_id(Context)
end.
-spec span_id() -> 0..16#FFFFFFFFFFFFFFFF.
span_id() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
Context = passage_span:get_context(Span),
jaeger_passage_span_context:get_span_id(Context)
end.
-spec tracer() -> passage:tracer_id().
tracer() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
passage_span:get_tracer(Span)
end.
-spec context() -> passage_span_contest:context().
context() ->
case passage_pd:current_span() of
undefined ->
undefined;
Span ->
passage_span:get_context(Span)
end.
-spec external_span(
TraceId :: passage:trace_id(),
SpanId :: undefined | passage:span_id(),
ParentSpanId :: undefined | passage:span_id()
) -> passage:maybe_span().
external_span(TraceId, undefined, ParentSpanId) ->
external_span(TraceId, rand:uniform(16#FFFFFFFFFFFFFFFF), ParentSpanId);
external_span(TraceId, SpanId, undefined) ->
external_span(TraceId, SpanId, rand:uniform(16#FFFFFFFFFFFFFFFF));
external_span(TraceId, SpanId, ParentSpanId) ->
IterFun = fun(Val) -> Val end,
Flags = <<0:32>>,
BaggageItems = <<0:32>>,
Binary = <<
TraceId:128,
SpanId:64,
ParentSpanId:64,
Flags/binary,
BaggageItems/binary
>>,
State = {ok, <<"binary">>, Binary, error},
passage:extract_span(?MAIN_TRACER, binary, IterFun, State).
match(OperationId, Tags) ->
OpMod = ctrace_config:filter_module_name(OperationId),
case erlang:function_exported(OpMod, match, 1) of
true ->
do_match(OpMod, Tags);
false ->
AllMod = ctrace_config:filter_module_name("all"),
case erlang:function_exported(AllMod, match, 1) of
true -> do_match(AllMod, Tags);
false -> false
end
end.
do_match(Mod, Tags) ->
case Mod:match(Tags) of
true ->
true;
false ->
false;
Rate when is_float(Rate) ->
rand:uniform() =< Rate
end.
add_tags(Options, ExtraTags) ->
case lists:keytake(tags, 1, Options) of
{value, {tags, T}, Opts} ->
[{tags, maps:merge(T, ExtraTags)} | Opts];
false ->
[{tags, ExtraTags} | Options]
end.
add_time(Options) ->
case lists:keymember(time, 1, Options) of
true ->
Options;
false ->
[{time, os:timestamp()} | Options]
end.
maybe_start_root(Options) ->
case lists:keytake(root, 1, Options) of
{value, {root, Root}, NewOptions} ->
[{child_of, Root} | NewOptions];
false ->
Options
end.
fun_to_op(Fun) ->
{module, M} = erlang:fun_info(Fun, module),
{name, F} = erlang:fun_info(Fun, name),
{arity, A} = erlang:fun_info(Fun, arity),
Str = io_lib:format("~s:~s/~b", [M, F, A]),
list_to_atom(lists:flatten(Str)).