Fix trace_id mismatch for logs emitted before Sentry::Rails::CaptureExceptions runs - #3016
Conversation
096916b to
080acef
Compare
|
Should I cut down on the inline code comments? |
@runephilosof yes please 🙏🏻 😄 public APIs should only get proper YARD coverage, any other inline comment is not needed except situations where some bit is really tricky to understand by just looking at the code. |
080acef to
f855b07
Compare
|
I have changed how the already established info is passed around, to make it less confusing and force pushed. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f855b07. Configure here.
Rails::Rack::Logger's "Started ..." line (and anything logged before CaptureExceptions runs) got an unrelated trace_id, since CaptureExceptions runs after ActionDispatch::ShowExceptions. Add Sentry::Rails::CaptureContext, a minimal middleware unshifted to the front of the stack that establishes the propagation context early. CaptureExceptions now consumes and reuses it instead of regenerating a new trace_id/span_id. Co-Authored-By: GitHub Copilot <noreply@example.com>
f855b07 to
99526a9
Compare

Fixes #3015
Problem
Sentry::Rails::CaptureExceptionsis deliberately positioned right afterActionDispatch::ShowExceptions(to skip transaction creation for static asset requests). That means anything logged before it runs - most notably Rails' ownRails::Rack::Logger"Started ..." line - gets atrace_id/span_idunrelated to the rest of the request, because no trace context has been established yet.There's also a second, compounding issue: even once
CaptureExceptionsdoes run, if the request isn't continuing an incoming distributed trace,Hub#start_transaction's fallback (Transaction.new(**options)) generates its own independenttrace_id, ignoring whatever was already on the scope's propagation context.Fix
Sentry::Rails::CaptureContextmiddleware, unshifted to the very front of the Rails middleware stack. It only establishes the propagation context (from incomingsentry-trace/baggageheaders, if present) as early as possible - it doesn't start a transaction or capture exceptions, soCaptureExceptions's existing position/behavior is untouched.Sentry::PropagationContext::ESTABLISHED_ENV_KEY- a new env flag signaling that trace context was already established for this exact request.Sentry::Rack::CaptureExceptionsreads and deletes this flag fromenvatomically, as the very first thing it does (before anything else that could raise), so it can never leak into later, unrelated reuses of the sameenv(e.g. Action Cable holding onto the handshake env for a connection's lifetime). It skips re-cloning the hub when the flag was present, and passes the result explicitly as anestablished:argument toHub#continue_traceandHub#start_transaction.Hub#continue_traceskips regenerating the propagation context whenestablished: true, instead of clobbering whatCaptureContextset up.Hub#start_transaction, when not continuing a distributed trace andestablished: true, builds the new transaction with the already-established trace_id/sample_rand instead of generating an unrelated one.The
start_transactionchange is intentionally scoped to only apply when theestablished:flag is passed (rather than always reusing whatever's on the scope), to avoid incorrectly linking together unrelated transactions that happen to share a thread (e.g. separate background jobs) - each of those still gets its own independent trace_id as before. This was verified against a regression insentry-rails' ActiveJob distributed-tracing specs during development.Testing
sentry-rails/spec/sentry/rails/capture_context_spec.rb, including an integration-style test that reproduces the original bug (probe middleware inserted before/afterCaptureExceptions) both with and without tracing enabled.sentry-ruby'shub/capture_exceptionsspecs for the new established-context guard behavior.sentry-rails' existing middleware-ordering spec to assertCaptureContextis the first middleware.sentry-rubyandsentry-railssuites locally; no regressions (Redis-dependent specs fail identically with/without this change due to no local Redis server, pre-existing/environment-only).