Current section
Files
Jump to
Current section
Files
src/telemetry/temporal_sdk_telemetry.erl
-module(temporal_sdk_telemetry).
% elp:ignore W0012 W0040 E1599
-moduledoc {file, "../../docs/telemetry/-module.md"}.
-export([
events/0,
events_by_prefix/1,
events_by_suffix/1,
handle_log/4
]).
-export([
setup/1
]).
-export([
execute/2,
execute/3,
execute/4,
spawn_execute/3,
spawn_execute/4
]).
-export([
do_execute/4
]).
-export([
otel_name/2,
otel_attributes/1,
otel_timestamp/0,
otel_timestamp_from_protobuf/1,
otel_native_to_timestamp/1,
otel_set_error/3,
otel_serialize/1
]).
-include("proto.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-doc """
Erlang exception.
""".
-type exception() :: {
Class :: error | exit | throw,
Reason :: term(),
Stacktrace :: erlang:raise_stacktrace() | erlang:stacktrace() | ExceptionDetails :: map()
}.
-export_type([exception/0]).
-doc """
Telemetry events handlers list used in the SDK node configuration.
List of tuples.
First tuple element is a list of telemetry events or a zero arity function returing such list.
Second tuple element is a handler function which will be used to handle the events list defined as
the first element.
Example:
```erlang
[
{
[
[temporal_sdk, workflow, task, start],
[temporal_sdk, activity, task, start]
],
fun my_app:handle_event/4
},
{
fun my_app:telemetry_events_list/0,
fun temporal_sdk_telemetry:handle_log/4
}
]
```
""".
-type events_handlers() ::
[
{
[telemetry:event_name()] | fun(() -> [telemetry:event_name()]),
telemetry:handler_function()
}
].
-export_type([events_handlers/0]).
-define(EVENT_NAME_PREFIX, temporal_sdk).
-define(DEFAULT_EXECUTE_TIMEOUT, 10_000).
-doc {file, "../../docs/telemetry/events-0.md"}.
-spec events() -> [telemetry:event_name()].
events() ->
[
%% temporal_sdk_node
[temporal_sdk, node, init],
[temporal_sdk, node, start],
[temporal_sdk, node, stats],
%% temporal_sdk_cluster
[temporal_sdk, cluster, init],
[temporal_sdk, cluster, start],
[temporal_sdk, cluster, exception],
[temporal_sdk, cluster, stats],
%% temporal_sdk_worker
[temporal_sdk, worker, init],
[temporal_sdk, worker, start],
[temporal_sdk, worker, stop],
[temporal_sdk, worker, exception],
[temporal_sdk, worker, stats],
%% temporal_sdk_activity
[temporal_sdk, activity, executor, start],
[temporal_sdk, activity, executor, stop],
[temporal_sdk, activity, executor, exception],
[temporal_sdk, activity, task, start],
[temporal_sdk, activity, task, stop],
[temporal_sdk, activity, task, exception],
[temporal_sdk, activity, execution, start],
[temporal_sdk, activity, execution, stop],
[temporal_sdk, activity, execution, exception],
%% temporal_sdk_nexus
[temporal_sdk, nexus, executor, start],
[temporal_sdk, nexus, executor, stop],
[temporal_sdk, nexus, executor, exception],
[temporal_sdk, nexus, task, start],
[temporal_sdk, nexus, task, stop],
[temporal_sdk, nexus, task, exception],
[temporal_sdk, nexus, execution, start],
[temporal_sdk, nexus, execution, stop],
[temporal_sdk, nexus, execution, exception],
%% temporal_sdk_workflow
[temporal_sdk, workflow, executor, start],
[temporal_sdk, workflow, executor, stop],
[temporal_sdk, workflow, executor, exception],
[temporal_sdk, workflow, task, start],
[temporal_sdk, workflow, task, stop],
[temporal_sdk, workflow, task, exception],
[temporal_sdk, workflow, execution, start],
[temporal_sdk, workflow, execution, stop],
[temporal_sdk, workflow, execution, exception],
[temporal_sdk, workflow, marker, start],
[temporal_sdk, workflow, marker, stop],
[temporal_sdk, workflow, marker, exception],
%% temporal_sdk_client
[temporal_sdk, client, init],
[temporal_sdk, client, start],
[temporal_sdk, client, exception],
%% temporal_sdk_grpc
[temporal_sdk, grpc, start],
[temporal_sdk, grpc, stop],
[temporal_sdk, grpc, exception],
%% temporal_sdk_poller
[temporal_sdk, poller, poll, start],
[temporal_sdk, poller, poll, stop],
[temporal_sdk, poller, poll, exception],
[temporal_sdk, poller, execute, start],
[temporal_sdk, poller, execute, stop],
[temporal_sdk, poller, execute, exception],
[temporal_sdk, poller, wait, start],
[temporal_sdk, poller, wait, stop],
%% temporal_sdk_limiter
[temporal_sdk, task_counter, node],
[temporal_sdk, task_counter, cluster],
[temporal_sdk, task_counter, worker]
].
-doc {file, "../../docs/telemetry/events_by_prefix-1.md"}.
-spec events_by_prefix(PrefixFilter :: telemetry:event_name()) -> [telemetry:event_name()].
events_by_prefix(PrefixFilter) -> do_events_pf(PrefixFilter, []).
do_events_pf([PF | TPrefixFilter], Acc) when is_atom(PF) ->
do_events_pf([[PF] | TPrefixFilter], Acc);
do_events_pf([PF | TPrefixFilter], Acc) when is_list(PF) ->
do_events_pf(TPrefixFilter, Acc ++ lists:filter(fun(E) -> lists:prefix(PF, E) end, events()));
do_events_pf([], Acc) ->
Acc.
-doc {file, "../../docs/telemetry/events_by_suffix-1.md"}.
-spec events_by_suffix(SuffixFilter :: telemetry:event_name()) -> [telemetry:event_name()].
events_by_suffix(SuffixFilter) -> do_events_sf(SuffixFilter, []).
do_events_sf([SF | TSuffixFilter], Acc) when is_atom(SF) ->
do_events_sf([[SF] | TSuffixFilter], Acc);
do_events_sf([SF | TSuffixFilter], Acc) when is_list(SF) ->
do_events_sf(TSuffixFilter, Acc ++ lists:filter(fun(E) -> lists:suffix(SF, E) end, events()));
do_events_sf([], Acc) ->
Acc.
-doc {file, "../../docs/telemetry/handle_log-4.md"}.
-spec handle_log(
Event :: telemetry:event_name(),
Measurements :: telemetry:event_measurements(),
Metadata :: telemetry:event_metadata(),
HandlerConfig :: telemetry:handler_config()
) -> any().
handle_log(Event, Measurements, Metadata, []) ->
LogLevel =
case lists:member(exception, Event) of
true -> error;
false -> notice
end,
M =
case Measurements of
#{duration := D} ->
Metadata#{duration => erlang:convert_time_unit(D, native, millisecond)};
#{} ->
Metadata
end,
?LOG(LogLevel, M#{telemetry_event => Event}).
-doc false.
-spec setup(NodeConfig :: map()) -> ok.
setup(#{telemetry_events_handlers := TEH}) when is_list(TEH) ->
do_setup(TEH, 1);
setup(#{telemetry_events_handlers := false}) ->
ok.
do_setup([{EventsFun, HandlerFun} | TTEH], HandlerId) when
is_function(EventsFun), is_function(HandlerFun)
->
do_setup([{EventsFun(), HandlerFun} | TTEH], HandlerId);
do_setup([{EventsList, HandlerFun} | TTEH], HandlerId) when
is_list(EventsList), is_function(HandlerFun)
->
HId = {?MODULE, HandlerId},
case telemetry:attach_many(HId, EventsList, HandlerFun, []) of
ok ->
ok;
Err ->
temporal_sdk_utils_logger:log_error(
Err,
?MODULE,
?FUNCTION_NAME,
"Error attaching telemetry handler to the event.",
#{handler_id => HId, events => EventsList, handler_function => HandlerFun}
)
end,
do_setup(TTEH, HandlerId + 1);
do_setup([], _HandlerId) ->
ok.
-doc false.
-spec execute(
EventName :: telemetry:event_name(),
Metadata :: telemetry:event_metadata()
) -> SystemTime :: integer().
execute(EventName, Metadata) ->
SystemTime = erlang:system_time(),
spawn_execute(EventName, Metadata, #{system_time => SystemTime}),
SystemTime.
-doc false.
-spec execute(
EventName :: telemetry:event_name(),
Metadata :: telemetry:event_metadata(),
MeasurementsOrStartTime :: map() | integer()
) -> SystemTime :: integer().
execute(EventName, Metadata, StartTime) when is_integer(StartTime) ->
SystemTime = erlang:system_time(),
Duration = SystemTime - StartTime,
spawn_execute(EventName, Metadata, #{duration => Duration}),
SystemTime;
execute(EventName, Metadata, Measurements) when is_map(Measurements) ->
SystemTime = erlang:system_time(),
spawn_execute(EventName, Metadata, Measurements),
SystemTime.
-doc false.
-spec execute(
EventName :: telemetry:event_name(),
Metadata :: telemetry:event_metadata(),
StartTime :: integer(),
MeasurementsOrException :: map() | exception()
) -> SystemTime :: integer().
execute(EventName, Metadata, StartTime, Measurements) when
is_integer(StartTime), is_map(Measurements)
->
SystemTime = erlang:system_time(),
Duration = SystemTime - StartTime,
spawn_execute(EventName, Metadata, Measurements#{duration => Duration}),
SystemTime;
execute(EventName, Metadata, StartTime, {Class, Reason, Stacktrace}) when is_integer(StartTime) ->
execute(
EventName,
Metadata#{class => Class, reason => do_reason(Class, Reason), stacktrace => Stacktrace},
StartTime
).
do_reason(Class, {Class, Err}) -> Err;
do_reason(Class, {Class, Err1, Err2}) -> {Err1, Err2};
do_reason(_Class, Err) -> Err.
-doc false.
-spec spawn_execute(
EventName :: telemetry:event_name(),
Metadata :: telemetry:event_metadata(),
Measurements :: telemetry:event_measurements() | telemetry:event_value()
) -> ok.
spawn_execute(EventName, Metadata, Measurements) ->
spawn_execute(EventName, Metadata, Measurements, ?DEFAULT_EXECUTE_TIMEOUT).
-doc false.
-spec spawn_execute(
EventName :: telemetry:event_name(),
Metadata :: telemetry:event_metadata(),
Measurements :: telemetry:event_measurements() | telemetry:event_value(),
Timeout :: non_neg_integer()
) -> ok.
spawn_execute(EventName, Metadata, Measurements, Timeout) ->
TEventName = [?EVENT_NAME_PREFIX | EventName],
Pid = spawn(?MODULE, do_execute, [TEventName, Metadata, Measurements, otel_ctx:get_current()]),
temporal_sdk_utils_proc:exit_after(Timeout, Pid).
-doc false.
do_execute(EventName, Metadata, Measurements, OtelCtx) ->
otel_ctx:attach(OtelCtx),
telemetry:execute(EventName, Measurements, Metadata).
-doc false.
-spec otel_name(Action :: unicode:chardata() | atom(), Id :: unicode:chardata() | atom()) ->
binary().
otel_name(Action, Id) when is_binary(Action), is_binary(Id) ->
<<Action/binary, ~":"/binary, Id/binary>>;
otel_name(Action, Id) when is_atom(Action) ->
otel_name(atom_to_binary(Action), Id);
otel_name(Action, Id) when is_atom(Id) ->
otel_name(Action, atom_to_binary(Id));
otel_name(Action, Id) ->
otel_name(
temporal_sdk_utils_unicode:characters_to_binary1(Action),
temporal_sdk_utils_unicode:characters_to_binary1(Id)
).
-doc false.
-spec otel_attributes(RawOtelAttr :: map()) -> OtelAttr :: map().
otel_attributes(RawOtelAttr) ->
BFn = fun
(V) when is_atom(V) -> atom_to_binary(V);
(V) -> temporal_sdk_utils_unicode:characters_to_binary1(V)
end,
Fn = fun
% common
(namespace, V, Acc) -> [{~"temporal.namespace", BFn(V)} | Acc];
(task_queue, V, Acc) -> [{~"temporal.task_queue", BFn(V)} | Acc];
% workflow
(workflow_id, V, Acc) -> [{~"temporal.workflow.id", BFn(V)} | Acc];
(workflow_type, V, Acc) -> [{~"temporal.workflow.type", BFn(V)} | Acc];
(workflow_run_id, V, Acc) -> [{~"temporal.workflow.run_id", BFn(V)} | Acc];
(execution_id, V, Acc) -> [{~"temporal.workflow.execution_id", BFn(V)} | Acc];
(marker_type, V, Acc) -> [{~"temporal.workflow.marker_type", BFn(V)} | Acc];
(marker_name, V, Acc) -> [{~"temporal.workflow.marker_name", BFn(V)} | Acc];
% activity
(activity_id, V, Acc) -> [{~"temporal.activity.id", BFn(V)} | Acc];
(activity_type, V, Acc) -> [{~"temporal.activity.type", BFn(V)} | Acc];
(A, V, Acc) when is_atom(A) -> [{atom_to_binary(A), BFn(V)} | Acc];
(A, V, Acc) -> [{BFn(A), BFn(V)} | Acc]
end,
maps:from_list(maps:fold(Fn, [], RawOtelAttr)).
-doc false.
-spec otel_timestamp() -> integer().
otel_timestamp() -> opentelemetry:timestamp().
-spec otel_timestamp_from_protobuf(ProtobufDuration :: ?TEMPORAL_SPEC:'google.protobuf.Duration'()) ->
integer().
otel_timestamp_from_protobuf(ProtobufDuration) ->
Nanos = temporal_sdk_utils_time:protobuf_to_nanos(ProtobufDuration),
erlang:convert_time_unit(Nanos, nanosecond, native) - erlang:time_offset().
-doc false.
-spec otel_native_to_timestamp(NativeTime :: integer()) -> integer().
otel_native_to_timestamp(NativeTime) ->
erlang:convert_time_unit(NativeTime, nanosecond, native) - erlang:time_offset().
-doc false.
-spec otel_set_error(
SpanCtx :: opentelemetry:span_ctx(), ErrorType :: term(), ErrorMessage :: term()
) ->
boolean().
otel_set_error(SpanCtx, ErrorType, ErrorMessage) ->
maybe
{ok, ET} ?= otel_utils:format_binary_string("~0tkP", [ErrorType, 10], [{chars_limit, 50}]),
{ok, EM} ?=
otel_utils:format_binary_string("~0tkP", [ErrorMessage, 10], [{chars_limit, 50}]),
otel_span:set_attributes(SpanCtx, [
{~"error", true}, {~"error.type", ET}, {~"error.message", EM}
]),
otel_span:set_status(SpanCtx, opentelemetry:status(?OTEL_STATUS_ERROR))
else
_ -> otel_span:set_status(SpanCtx, opentelemetry:status(?OTEL_STATUS_ERROR))
end.
-doc false.
-spec otel_serialize(Term :: term()) -> binary().
otel_serialize(Term) ->
case otel_utils:format_binary_string("~0tkP", [Term, 10], [{chars_limit, 50}]) of
{ok, Bin} -> Bin;
_ -> ~"bad_binary_conversion"
end.