-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add record_exceptions options to with_span #622
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention:
... and 1 file with indirect coverage changes 📢 Thoughts on this report? Let us know!. |
record_exception(_, _, _, _, _) -> | ||
false. | ||
|
||
exception_type(error, #{'__exception__' := true, '__struct__' := ElixirErrorStruct}) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, any danger in doing this instead of creating a new with_span
in the Elixir macro instead of calling Erlang's with_span
function?
I like to keep just one with_span
function and hadn't considered we could do something like this to handle the exception case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replicating the whole with_span
macro on Elixir side is the fallback option but first I wanted to explore this solution and I am quite satisfied with the result.
This piece of code fails if someone is doing
erlang:error(#{'__exception__' => true, '__struct__' => foo).
but it seems very unlikely. We can anyway protect from such cases and fallback to the normal type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the call to Elixir.Exception.message()
should be modified so that it doesn't fail in any circumstance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about the relying on the internal structure of Elixir's structs and exceptions. Probably unlikely they change, but was my initial hesitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, I think that structure is not internal Exception.t() is not an opaque type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we may ask someone more involved with Elixir development, I don't know if there is some contributor here that can answer
0fb52b6
to
a72c344
Compare
Sorry for the delay. I'm not sure what to do about |
The only ways I can think of are:
I'm usually against code duplication but in this case I'm not sure the complexity needed for avoiding duplication is worth. |
The problem with duplication is the logic is in the SDK and there is only the Erlang SDK. We'd have to have the logic in the API to add it to Elixir. Hm, couldn't this just convert Erlang atoms like badarg to ArgumentError? |
The problem is that in both these cases %% ERROR
?assertException(error, badarg, otel_tracer:with_span(Tracer, <<"span-error">>, #{record_exception => true},
fun(_SpanCtx) ->
erlang:error(badarg)
end)),
receive
{span, SpanError} ->
?assertEqual(<<"span-error">>, SpanError#span.name),
?assertEqual(undefined, SpanError#span.status),
[#event{name=exception, attributes=A}] = otel_events:list(SpanError#span.events),
?assertMatch(#{'exception.type' := <<"error:badarg">>, 'exception.stacktrace' := _}, otel_attributes:map(A))
after
1000 ->
ct:fail(timeout)
end, and assert_raise ArgumentError, fn ->
Tracer.with_span "span-1", record_exception: true do
:erlang.error(:badarg)
end
end
assert_receive {:span,
span(
name: "span-1",
events: {:events, _, _, _, _, [event]},
status: :undefined
)}
assert event(name: :exception, attributes: {:attributes, _, _, _, received_attirbutes}) =
event
assert %{
"exception.type": "error:badarg",
"exception.stacktrace": _
} = received_attirbutes the |
Yea, good point. I don't know what to do here. Maybe it has to stay as @bryannaegele any thoughts or should we merge this? |
I don't know if it's 1:1 correlation to here, but Phoenix does normalization of erlang exceptions to something elixir can handle. |
There is something similar also in Elixir core, where Erlang exception are translated to Elixir ones. This is exactly what is causing problems here: the logic for recording the exception is in the SDK (Erlang side) and we need to know if the with_span is called from Erlang or Elixir in order to decide whether the exception should be translated to Elixir before being recorded. The only way for doing so that I can think of is passing an argument to the with_span macro |
a72c344
to
7c01f87
Compare
I may have found a solution. I take the first element of the stacktrace and use it to know if the exception was raised from Erlang or Elixir so that I can decide if the exception need to be translated or not. In this way we also simplify the custom |
status: status(code: :error) | ||
)} | ||
|
||
assert event(name: :exception, attributes: {:attributes, _, _, _, received_attirbutes}) = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor spelling mistake: /s/received_attirbutes/received_attributes
Fixes #236
The Python API has been taken as a reference.
There is still something that does not fully convince me.
Elixir exceptions are recorded with
exception.type
equal to the name of the exception struct (see test).Standard erlang errors are mapped by Elixir to exception structs so when in Elixir, inside a trace, an exception is raised with
:erlang.error(type)
the exception is recorded with the erlang type (e.g.error:badarg
) but the user get anArgumentError
(see test).