Skip to content

Commit 3d26b62

Browse files
authored
Merge pull request #138 from gugahoa/add-record-exception
Add RecordException to Elixir's Span module
2 parents 3995f77 + 37f5559 commit 3d26b62

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

apps/opentelemetry/test/opentelemetry_SUITE.erl

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ all() ->
1919
all_testcases() ->
2020
[with_span, macros, child_spans, update_span_data, tracer_instrumentation_library,
2121
tracer_previous_ctx, stop_temporary_app, reset_after, attach_ctx, default_sampler,
22-
record_but_not_sample].
22+
record_but_not_sample, record_exception_works, record_exception_with_message_works].
2323

2424
groups() ->
2525
[{w3c, [], [propagation]},
@@ -456,6 +456,47 @@ record_but_not_sample(Config) ->
456456

457457
ok.
458458

459+
record_exception_works(Config) ->
460+
Tid = ?config(tid, Config),
461+
SpanCtx = ?start_span(<<"span-1">>),
462+
try throw(my_error) of
463+
_ ->
464+
ok
465+
catch
466+
Class:Term:Stacktrace ->
467+
otel_span:record_exception(SpanCtx, Class, Term, Stacktrace, [{"some-attribute", "value"}]),
468+
?end_span(SpanCtx),
469+
[Span] = assert_exported(Tid, SpanCtx),
470+
[Event] = Span#span.events,
471+
?assertEqual(<<"exception">>, Event#event.name),
472+
?assertEqual([{<<"exception.type">>, <<"throw:my_error">>},
473+
{<<"exception.stacktrace">>, list_to_binary(io_lib:format("~p", [Stacktrace], [{chars_limit, 50}]))},
474+
{"some-attribute","value"}],
475+
Event#event.attributes),
476+
ok
477+
end.
478+
479+
record_exception_with_message_works(Config) ->
480+
Tid = ?config(tid, Config),
481+
SpanCtx = ?start_span(<<"span-1">>),
482+
try throw(my_error) of
483+
_ ->
484+
ok
485+
catch
486+
Class:Term:Stacktrace ->
487+
otel_span:record_exception(SpanCtx, Class, Term, "My message", Stacktrace, [{"some-attribute", "value"}]),
488+
?end_span(SpanCtx),
489+
[Span] = assert_exported(Tid, SpanCtx),
490+
[Event] = Span#span.events,
491+
?assertEqual(<<"exception">>, Event#event.name),
492+
?assertEqual([{<<"exception.type">>, <<"throw:my_error">>},
493+
{<<"exception.stacktrace">>, list_to_binary(io_lib:format("~p", [Stacktrace], [{chars_limit, 50}]))},
494+
{<<"exception.message">>, "My message"},
495+
{"some-attribute","value"}],
496+
Event#event.attributes),
497+
ok
498+
end.
499+
459500
%%
460501

461502
assert_all_exported(Tid, SpanCtxs) ->

apps/opentelemetry_api/lib/open_telemetry/span.ex

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,34 @@ defmodule OpenTelemetry.Span do
9595
@spec add_events(OpenTelemetry.span_ctx(), [OpenTelemetry.event()]) :: boolean()
9696
defdelegate add_events(span_ctx, events), to: :otel_span
9797

98+
defguardp is_exception?(term)
99+
when is_map(term) and :erlang.is_map_key(:__struct__, term) and
100+
is_atom(:erlang.map_get(:__struct__, term)) and
101+
:erlang.is_map_key(:__exception__, term) and
102+
:erlang.map_get(:__exception__, term) == true
103+
104+
@doc """
105+
Record an exception as an event, following the semantics convetions for exceptions.
106+
107+
If trace is not provided, the stacktrace is retrieved from `Process.info/2`
108+
"""
109+
@spec record_exception(OpenTelemetry.span_ctx(), Exception.t()) :: boolean()
110+
def record_exception(span_ctx, exception, trace \\ nil, attributes \\ [])
111+
112+
def record_exception(span_ctx, exception, trace, attributes) when is_exception?(exception) do
113+
exception_type = to_string(exception.__struct__)
114+
115+
exception_attributes = [
116+
{"exception.type", exception_type},
117+
{"exception.message", Exception.message(exception)},
118+
{"exception.stacktrace", Exception.format_stacktrace(trace)}
119+
]
120+
121+
add_event(span_ctx, "exception", exception_attributes ++ attributes)
122+
end
123+
124+
def record_exception(_, _, _, _), do: false
125+
98126
@doc """
99127
Sets the Status of the currently active Span.
100128

apps/opentelemetry_api/src/otel_span.erl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
set_attributes/2,
2828
add_event/3,
2929
add_events/2,
30+
record_exception/5,
31+
record_exception/6,
3032
set_status/2,
3133
update_name/2,
3234
end_span/1]).
@@ -105,6 +107,30 @@ add_events(SpanCtx=#span_ctx{span_sdk={Module, _}}, Events) when ?is_recording(S
105107
add_events(_, _) ->
106108
false.
107109

110+
-spec record_exception(SpanCtx, Class, Term, Stacktrace, Attributes) -> boolean() when
111+
SpanCtx :: opentelemetry:span_ctx(),
112+
Class :: atom(),
113+
Term :: term(),
114+
Stacktrace :: list(any()),
115+
Attributes :: opentelemetry:attributes().
116+
record_exception(SpanCtx, Class, Term, Stacktrace, Attributes) ->
117+
ExceptionAttributes = [{<<"exception.type">>, iolist_to_binary(io_lib:format("~0tP:~0tP", [Class, 10, Term, 10], [{chars_limit, 50}]))},
118+
{<<"exception.stacktrace">>, iolist_to_binary(io_lib:format("~0tP", [Stacktrace, 10], [{chars_limit, 50}]))}],
119+
add_event(SpanCtx, <<"exception">>, ExceptionAttributes ++ Attributes).
120+
121+
-spec record_exception(SpanCtx, Class, Term, Message, Stacktrace, Attributes) -> boolean() when
122+
SpanCtx :: opentelemetry:span_ctx(),
123+
Class :: atom(),
124+
Term :: term(),
125+
Message :: unicode:unicode_binary(),
126+
Stacktrace :: list(any()),
127+
Attributes :: opentelemetry:attributes().
128+
record_exception(SpanCtx, Class, Term, Message, Stacktrace, Attributes) ->
129+
ExceptionAttributes = [{<<"exception.type">>, iolist_to_binary(io_lib:format("~0tP:~0tP", [Class, 10, Term, 10], [{chars_limit, 50}]))},
130+
{<<"exception.stacktrace">>, iolist_to_binary(io_lib:format("~0tP", [Stacktrace, 10], [{chars_limit, 50}]))},
131+
{<<"exception.message">>, Message}],
132+
add_event(SpanCtx, <<"exception">>, ExceptionAttributes ++ Attributes).
133+
108134
-spec set_status(SpanCtx, Status) -> boolean() when
109135
Status :: opentelemetry:status(),
110136
SpanCtx :: opentelemetry:span_ctx().

test/otel_tests.exs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ defmodule OtelTests do
88
require Record
99
@fields Record.extract(:span, from_lib: "opentelemetry/include/otel_span.hrl")
1010
Record.defrecordp(:span, @fields)
11+
1112
@fields Record.extract(:span_ctx, from_lib: "opentelemetry_api/include/opentelemetry.hrl")
1213
Record.defrecordp(:span_ctx, @fields)
1314

15+
@event_fields Record.extract(:event, from_lib: "opentelemetry_api/include/opentelemetry.hrl")
16+
Record.defrecordp(:event, @event_fields)
17+
1418
test "use Tracer to set current active Span's attributes" do
1519
:otel_batch_processor.set_exporter(:otel_exporter_pid, self())
1620
OpenTelemetry.register_tracer(:test_tracer, "0.1.0")
@@ -200,4 +204,40 @@ defmodule OtelTests do
200204
parent_span_id: :undefined
201205
)}
202206
end
207+
208+
test "Span.record_exception/4 should return false if passed an invalid exception" do
209+
Tracer.with_span "span-3" do
210+
refute OpenTelemetry.Span.record_exception(Tracer.current_span_ctx(), :not_an_exception)
211+
end
212+
end
213+
214+
test "Span.record_exception/4 should add an exception event to the span" do
215+
:otel_batch_processor.set_exporter(:otel_exporter_pid, self())
216+
s = Tracer.start_span("span-4")
217+
218+
try do
219+
raise RuntimeError, "my error message"
220+
rescue
221+
ex ->
222+
assert Span.record_exception(s, ex, __STACKTRACE__)
223+
assert Span.end_span(s)
224+
225+
stacktrace = Exception.format_stacktrace(__STACKTRACE__)
226+
227+
assert_receive {:span,
228+
span(
229+
name: "span-4",
230+
events: [
231+
event(
232+
name: "exception",
233+
attributes: [
234+
{"exception.type", "Elixir.RuntimeError"},
235+
{"exception.message", "my error message"},
236+
{"exception.stacktrace", ^stacktrace}
237+
]
238+
)
239+
]
240+
)}
241+
end
242+
end
203243
end

0 commit comments

Comments
 (0)