feat(webapp): link Sentry events to OTel traces via trace_id#3531
feat(webapp): link Sentry events to OTel traces via trace_id#3531
Conversation
Stamps the active OpenTelemetry trace_id and span_id onto every Sentry event captured from the webapp, plus an otel_sampled tag indicating whether the corresponding trace was head-sampled. Engineers can now copy the trace_id from any Sentry issue and search their tracing backend by it directly. Implemented as a single global Sentry event processor registered after Sentry.init in apps/webapp/sentry.server.ts. The processor reads the active OTel context via @opentelemetry/api and writes Sentry's native contexts.trace fields. No tracer config or sampling changes; no client-side Sentry init exists in this codebase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a code comment on addOtelTraceContextToEvent explaining that overwriting Sentry's contexts.trace.trace_id is the design intent — with skipOpenTelemetrySetup: true, Sentry's auto-generated trace_id is unrelated to OTel, and replacing it is the whole point of the processor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Caution Review failedFailed to post review comments WalkthroughThe pull request adds OpenTelemetry integration to Sentry to stamp the active OTel trace_id and span_id onto Sentry events for cross-referencing with traces in any OTel backend. Two new utility functions are introduced: Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
Stamps the active OpenTelemetry
trace_idandspan_idonto every Sentry event captured from the webapp, so engineers can copy atrace_idfrom a Sentry issue and search for the corresponding trace in any OTel-aware backend. Also adds anotel_sampledtag to indicate whether the trace was head-sampled — a cheap signal for whether the link will resolve to span data or hit a missing trace.Why
Sentry and OTel were OTel-disconnected:
apps/webapp/sentry.server.tsinitialised Sentry withskipOpenTelemetrySetup: true, and no error-capture site (logger.server.ts, the Remix-wrappedhandleError, the rootErrorBoundary) attached OTel context to the event. With many spans/sec across services, getting from a Sentry issue to its trace was guesswork.Approach
Single global Sentry event processor, registered immediately after
Sentry.init. On each event it readstrace.getActiveSpan()?.spanContext()via@opentelemetry/api, then writes:event.contexts.trace.trace_idandevent.contexts.trace.span_id(Sentry's native trace context fields)event.tags.otel_sampled="true"|"false"(derived fromtraceFlags)If no active span (module-load errors, scheduled timers without a context, primary cluster process), the processor returns the event unmodified — Sentry's default propagation context fills in.
Implementation is co-located in
apps/webapp/sentry.server.ts(no separate helper module —sentry.server.tsis built standalone by esbuild and a separate import would have required a new bundling step). Helper functions are exported so the unit tests can reach them without re-runningSentry.init.Non-goals (deliberate)
trace_idthat returns no spans in the tracing backend (head-sampled out). Theotel_sampledtag makes that obvious at a glance. Raising find-rate is a separate conversation with cost trade-offs.Sentry.setUser(would need auth-helper + per-request scope wiring across multiple worker entrypoints — separate ticket).Test plan
apps/webapp/test/sentryTraceContext.server.test.ts— 9 tests covering: helper returns `undefined` with no active span; returns `traceId`/`spanId`/`sampled=true` for a recording span; returns `sampled=false` for a non-recording span; processor leaves the event unchanged with no active span; processor stamps `trace_id`/`span_id` onto `contexts.trace`; preserves existing `contexts.trace` fields; tags `otel_sampled` correctly for both sampled and non-sampled cases; never throws if `@opentelemetry/api` access throws.