%%%------------------------------------------------------------------------ %% 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 The types defined here, and referencing records in opentelemetry.hrl %% are used to store trace information while being collected on the %% Erlang node. %% %% Thus, while the types are based on protos found in the opentelemetry-proto %% repo: src/opentelemetry/proto/trace/v1/trace.proto, %% they are not exact translations because further processing is done after %% the span has finished and can be vendor specific. For example, there is %% no count of the number of dropped attributes in the span record. And %% an attribute's value can be a function to only evaluate the value if it %% is actually used (at the time of exporting). And the stacktrace is a %% regular Erlang stack trace. %% @end %%%------------------------------------------------------------------------- -module(opentelemetry). -export([set_default_tracer/1, set_tracer/2, set_meter/2, set_default_meter/1, set_default_context_manager/1, get_context_manager/0, register_tracer/2, register_application_tracer/1, register_meter/2, register_application_meter/1, get_tracer/0, get_tracer/1, get_meter/0, get_meter/1, set_http_extractor/1, get_http_extractor/0, set_http_injector/1, get_http_injector/0, timestamp/0, links/1, link/4, event/2, event/3, events/1, status/2, generate_trace_id/0, generate_span_id/0]). -include("opentelemetry.hrl"). -include_lib("kernel/include/logger.hrl"). -export_type([tracer/0, meter/0, trace_id/0, span_id/0, span_name/0, span_ctx/0, span/0, span_kind/0, link/0, links/0, attribute_key/0, attribute_value/0, attributes/0, event/0, events/0, stack_trace/0, tracestate/0, status/0, resource/0, http_headers/0]). -type tracer() :: {module(), term()}. -type meter() :: {module(), term()}. -type trace_id() :: non_neg_integer(). -type span_id() :: non_neg_integer(). -type span_ctx() :: #span_ctx{}. -type span() :: term(). -type span_name() :: unicode:unicode_binary(). -type attribute_key() :: unicode:unicode_binary(). -type attribute_value() :: any(). -type attribute() :: {unicode:unicode_binary(), attribute_value()}. -type attributes() :: [attribute()]. -type span_kind() :: ?SPAN_KIND_UNSPECIFIED | ?SPAN_KIND_INTERNAL | ?SPAN_KIND_SERVER | ?SPAN_KIND_CLIENT | ?SPAN_KIND_PRODUCER | ?SPAN_KIND_CONSUMER. -type event() :: #event{}. -type events() :: [#event{}]. -type link() :: #link{}. -type links() :: [#link{}]. -type status() :: #status{}. %% The key must begin with a lowercase letter, and can only contain %% lowercase letters 'a'-'z', digits '0'-'9', underscores '_', dashes %% '-', asterisks '*', and forward slashes '/'. %% The value is opaque string up to 256 characters printable ASCII %% RFC0020 characters (i.e., the range 0x20 to 0x7E) except ',' and '='. %% Note that this also excludes tabs, newlines, carriage returns, etc. -type tracestate() :: [{unicode:latin1_chardata(), unicode:latin1_chardata()}]. -type stack_trace() :: [erlang:stack_item()]. -type resource() :: #{unicode:unicode_binary() => unicode:unicode_binary()}. -type http_headers() :: [{unicode:unicode_binary(), unicode:unicode_binary()}]. -spec set_default_tracer(tracer()) -> boolean(). set_default_tracer(Tracer) -> verify_and_set_term(Tracer, default_tracer, ot_tracer). -spec set_tracer(atom(), tracer()) -> boolean(). set_tracer(Name, Tracer) -> verify_and_set_term(Tracer, Name, ot_tracer). -spec set_default_meter(meter()) -> boolean(). set_default_meter(Meter) -> verify_and_set_term(Meter, default_meter, ot_meter). -spec set_meter(atom(), meter()) -> boolean(). set_meter(Name, Meter) -> verify_and_set_term(Meter, Name, ot_meter). -spec set_default_context_manager(ot_ctx:context_manager()) -> boolean(). set_default_context_manager(ContextManager) -> verify_and_set_term(ContextManager, context_manager, ot_ctx). -spec register_tracer(atom(), string()) -> boolean(). register_tracer(Name, Vsn) -> ot_tracer_provider:register_tracer(Name, Vsn). -spec register_application_tracer(atom()) -> boolean(). register_application_tracer(Name) -> ot_tracer_provider:register_application_tracer(Name). -spec register_meter(atom(), string()) -> boolean(). register_meter(Name, Vsn) -> ot_meter_provider:register_meter(Name, Vsn). -spec register_application_meter(atom()) -> boolean(). register_application_meter(Name) -> ot_meter_provider:register_application_meter(Name). -spec get_context_manager() -> ot_ctx:context_manager(). get_context_manager() -> persistent_term:get({?MODULE, context_manager}, {ot_ctx_noop, []}). -spec get_tracer() -> tracer(). get_tracer() -> persistent_term:get({?MODULE, default_tracer}, {ot_tracer_noop, []}). -spec get_tracer(atom()) -> tracer(). get_tracer(Name) -> persistent_term:get({?MODULE, Name}, get_tracer()). -spec get_meter() -> meter(). get_meter() -> persistent_term:get({?MODULE, default_meter}, {ot_meter_noop, []}). -spec get_meter(atom()) -> meter(). get_meter(Name) -> persistent_term:get({?MODULE, Name}, get_meter()). set_http_extractor(List) when is_list(List) -> persistent_term:put({?MODULE, http_extractor}, List); set_http_extractor(_) -> ok. set_http_injector(List) when is_list(List) -> persistent_term:put({?MODULE, http_injector}, List); set_http_injector(_) -> ok. get_http_extractor() -> persistent_term:get({?MODULE, http_extractor}, []). get_http_injector() -> persistent_term:get({?MODULE, http_injector}, []). -spec timestamp() -> integer(). timestamp() -> erlang:system_time(nanosecond). -spec links([{TraceId, SpanId, Attributes, TraceState}]) -> links() when TraceId :: trace_id(), SpanId :: span_id(), Attributes :: attributes(), TraceState :: tracestate(). links(List) -> [link(TraceId, SpanId, Attributes, TraceState) || {TraceId, SpanId, Attributes, TraceState} <- List, is_integer(TraceId), is_integer(SpanId), is_list(Attributes), is_list(TraceState)]. -spec link(TraceId, SpanId, Attributes, TraceState) -> link() | undefined when TraceId :: trace_id(), SpanId :: span_id(), Attributes :: attributes(), TraceState :: tracestate(). link(TraceId, SpanId, Attributes, TraceState) when is_integer(TraceId), is_integer(SpanId), is_list(Attributes), is_list(TraceState) -> #link{trace_id=TraceId, span_id=SpanId, attributes=Attributes, tracestate=TraceState}; link(_, _, _, _) -> undefined. -spec event(Name, Attributes) -> event() | undefined when Name :: unicode:unicode_binary(), Attributes :: attributes(). event(Name, Attributes) when is_binary(Name), is_list(Attributes) -> #event{time=wts:timestamp(), name=Name, attributes=Attributes}; event(_, _) -> undefined. -spec event(Time, Name, Attributes) -> event() | undefined when Time :: wts:timestamp() | non_neg_integer(), Name :: unicode:unicode_binary(), Attributes :: attributes(). event(Timestamp={Time, Offset}, Name, Attributes) when is_integer(Time), is_integer(Offset), is_binary(Name), is_list(Attributes) -> #event{time=Timestamp, name=Name, attributes=Attributes}; event(TimeNano, Name, Attributes) when is_integer(TimeNano), is_binary(Name), is_list(Attributes) -> #event{time=TimeNano, name=Name, attributes=Attributes}; event(_, _, _) -> undefined. events(List) -> Timestamp = wts:timestamp(), lists:filtermap(fun({Time, Name, Attributes}) when is_binary(Name), is_list(Attributes) -> case event(Time, Name, Attributes) of undefined -> false; Event -> {true, Event} end; ({Name, Attributes}) when is_binary(Name), is_list(Attributes) -> case event(Timestamp, Name, Attributes) of undefined -> false; Event -> {true, Event} end; (_) -> false end, List). -spec status(Code, Message) -> status() | undefined when Code :: atom(), Message :: unicode:unicode_binary(). status(Code, Message) when is_atom(Code), is_binary(Message) -> #status{code=Code, message=Message}; status(_, _) -> undefined. %%-------------------------------------------------------------------- %% @doc %% Generates a 128 bit random integer to use as a trace id. %% @end %%-------------------------------------------------------------------- -spec generate_trace_id() -> trace_id(). generate_trace_id() -> uniform(2 bsl 127 - 1). %% 2 shifted left by 127 == 2 ^ 128 %%-------------------------------------------------------------------- %% @doc %% Generates a 64 bit random integer to use as a span id. %% @end %%-------------------------------------------------------------------- -spec generate_span_id() -> span_id(). generate_span_id() -> uniform(2 bsl 63 - 1). %% 2 shifted left by 63 == 2 ^ 64 uniform(X) -> rand:uniform(X). %% internal functions -spec verify_and_set_term(module() | {module(), term()}, term(), atom()) -> boolean(). verify_and_set_term(Module, TermKey, Behaviour) -> case verify_behaviour(Module, Behaviour) of true -> persistent_term:put({?MODULE, TermKey}, Module), true; false -> ?LOG_WARNING("Module ~p does not implement behaviour ~p. " "A noop ~p will be used until a module implementing " "the behaviour is configured.", [Module, Behaviour, Behaviour]), false end. -spec verify_behaviour(module() | {module(), term()}, atom()) -> boolean(). verify_behaviour({Module, _}, Behaviour) -> verify_behaviour(Module, Behaviour); verify_behaviour(Module, Behaviour) -> try Module:module_info(attributes) of Attributes -> case lists:keyfind(behaviour, 1, Attributes) of {behaviour, Behaviours} -> lists:member(Behaviour, Behaviours); _ -> false end catch error:undef -> false end.