Summary
Error events are the only event type the SDK sends without trace context. Sentry.Event.create_event/1 sets contexts: to generate_contexts(), which returns %{os: ..., runtime: ...} and nothing else, and no later stage adds a :trace key. So an exception captured inside an active span arrives at Sentry with no link to the trace it happened in, even when the SDK itself exported that trace over the same transport moments earlier.
The visible symptom is that the issue's trace_id equals its event_id — Relay fabricates a trace id from the event id when the payload carries none. In the UI the issue's "Trace" tab shows only itself, and none of the spans, logs or other errors from the same request appear.
Why this looks like an oversight rather than a decision
Three of the four event types the SDK sends already carry trace context, and the fourth documents it as the way to reach errors:
- Transactions carry it:
Sentry.Transaction's typespec makes contexts.trace required, and Sentry.OpenTelemetry.SpanProcessor.build_trace_context/1 fills it in.
- Logs carry it:
Sentry.LogEvent.extract_trace_context/1 reads trace_id/span_id out of logger metadata (as written there by opentelemetry_logger_metadata), and falls back to Sentry.UUID.uuid4_hex() when absent.
- Check-ins carry it, and the option's own docs say why: "right now Sentry supports the
trace_id key under the trace context to connect the check-in with related errors" (Sentry.CheckIn, the :contexts schema entry).
- Errors carry nothing.
So the SDK will happily connect a cron check-in to related errors, but the errors themselves cannot be connected to anything. The docs for check-ins describe a linkage that the error side of does not exist.
There is also no way for a user to supply it: Sentry.Context.context_keys/0 is [:breadcrumbs, :tags, :user, :extra, :request, :attachments], with no trace slot, so Sentry.Context cannot be used to work around this either.
Reproduction
With tracing configured (Sentry.OpenTelemetry.SpanProcessor + Sentry.OpenTelemetry.Sampler, traces_sample_rate: 1.0):
require OpenTelemetry.Tracer, as: Tracer
Tracer.with_span "repro" do
# The SDK knows the trace here — it is about to export it as a transaction.
IO.inspect(:otel_span.hex_trace_id(Tracer.current_span_ctx()), label: "active trace")
event = Sentry.Event.create_event(message: "hello")
IO.inspect(event.contexts, label: "contexts")
# => contexts: %{os: %{...}, runtime: %{...}} <- no :trace
Sentry.capture_message("hello")
end
In Sentry, the resulting issue's trace_id is its own event_id, not the trace id printed above.
Expected
An event captured while a valid (non-zero, non-noop) span context is in scope should go out with contexts.trace set from that span — at minimum trace_id and span_id, formatted as the transaction path already formats them, so the two agree. With no active span, behaviour should stay exactly as it is today.
This looks cheap to do centrally: :before_send already runs synchronously in the capturing process (Sentry.Client.send_event/2 → maybe_call_before_send/2 → function.(event), no task or spawn), and both the plug capture path and the crash-report handler run in the process that owns the span, so the span context is simply in scope at event-creation time.
Workaround
For anyone else hitting this, a :before_send callback covers it in about ten lines:
def call(%Sentry.Event{} = event) do
ctx = :otel_tracer.current_span_ctx()
if :otel_span.is_valid(ctx) do
trace = %{trace_id: :otel_span.hex_trace_id(ctx), span_id: :otel_span.hex_span_id(ctx)}
%{event | contexts: Map.put(event.contexts || %{}, :trace, trace)}
else
event
end
end
def call(other), do: other
Two notes for anyone copying it: contexts is nil on a hand-built %Sentry.Event{} (the struct default — only create_event/1 fills it in), and the callback also receives transactions, so it has to pass through anything that is not an %Sentry.Event{} or it takes tracing down with it.
Versions
Verified against sentry 12.0.3 from Hex, reading lib/sentry/event.ex, lib/sentry/context.ex, lib/sentry/client.ex, lib/sentry/plug_capture.ex and lib/sentry/opentelemetry/. I also checked master: create_event/1 still assigns contexts: generate_contexts(), generate_contexts/0 still returns os and runtime only, and client.ex still attaches no trace on the send path. I have not run 13.4.2 directly, so if this was addressed somewhere I did not look, apologies for the noise.
I could not find an existing issue covering this — #1011 and #1163 both concern spans going missing from traces, which is a different failure.
Happy to open a PR if you would like it fixed the way described above, or a different way if you would rather it live somewhere other than create_event/1.
Summary
Error events are the only event type the SDK sends without trace context.
Sentry.Event.create_event/1setscontexts:togenerate_contexts(), which returns%{os: ..., runtime: ...}and nothing else, and no later stage adds a:tracekey. So an exception captured inside an active span arrives at Sentry with no link to the trace it happened in, even when the SDK itself exported that trace over the same transport moments earlier.The visible symptom is that the issue's
trace_idequals itsevent_id— Relay fabricates a trace id from the event id when the payload carries none. In the UI the issue's "Trace" tab shows only itself, and none of the spans, logs or other errors from the same request appear.Why this looks like an oversight rather than a decision
Three of the four event types the SDK sends already carry trace context, and the fourth documents it as the way to reach errors:
Sentry.Transaction's typespec makescontexts.tracerequired, andSentry.OpenTelemetry.SpanProcessor.build_trace_context/1fills it in.Sentry.LogEvent.extract_trace_context/1readstrace_id/span_idout of logger metadata (as written there byopentelemetry_logger_metadata), and falls back toSentry.UUID.uuid4_hex()when absent.trace_idkey under the trace context to connect the check-in with related errors" (Sentry.CheckIn, the:contextsschema entry).So the SDK will happily connect a cron check-in to related errors, but the errors themselves cannot be connected to anything. The docs for check-ins describe a linkage that the error side of does not exist.
There is also no way for a user to supply it:
Sentry.Context.context_keys/0is[:breadcrumbs, :tags, :user, :extra, :request, :attachments], with no trace slot, soSentry.Contextcannot be used to work around this either.Reproduction
With tracing configured (
Sentry.OpenTelemetry.SpanProcessor+Sentry.OpenTelemetry.Sampler,traces_sample_rate: 1.0):In Sentry, the resulting issue's
trace_idis its ownevent_id, not the trace id printed above.Expected
An event captured while a valid (non-zero, non-noop) span context is in scope should go out with
contexts.traceset from that span — at minimumtrace_idandspan_id, formatted as the transaction path already formats them, so the two agree. With no active span, behaviour should stay exactly as it is today.This looks cheap to do centrally:
:before_sendalready runs synchronously in the capturing process (Sentry.Client.send_event/2→maybe_call_before_send/2→function.(event), no task or spawn), and both the plug capture path and the crash-report handler run in the process that owns the span, so the span context is simply in scope at event-creation time.Workaround
For anyone else hitting this, a
:before_sendcallback covers it in about ten lines:Two notes for anyone copying it:
contextsisnilon a hand-built%Sentry.Event{}(the struct default — onlycreate_event/1fills it in), and the callback also receives transactions, so it has to pass through anything that is not an%Sentry.Event{}or it takes tracing down with it.Versions
Verified against
sentry 12.0.3from Hex, readinglib/sentry/event.ex,lib/sentry/context.ex,lib/sentry/client.ex,lib/sentry/plug_capture.exandlib/sentry/opentelemetry/. I also checkedmaster:create_event/1still assignscontexts: generate_contexts(),generate_contexts/0still returns os and runtime only, andclient.exstill attaches no trace on the send path. I have not run 13.4.2 directly, so if this was addressed somewhere I did not look, apologies for the noise.I could not find an existing issue covering this — #1011 and #1163 both concern spans going missing from traces, which is a different failure.
Happy to open a PR if you would like it fixed the way described above, or a different way if you would rather it live somewhere other than
create_event/1.