Send each request under the trace context stored on it - #172
Merged
Conversation
The Guzzle auto-instrumentation strips traceparent/tracestate off the outgoing request and re-injects them from the active context. Since the pool sends every request in a chunk under the worker's own span, all of them reached their destination sharing that one parent, and the trace context recorded when the row was created never made it onto the wire. Activating the row's own context around the send makes the client span parent to the trace that created the request insurance instead.
firecow
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every request sent by the worker arrives at its destination under the same parent span, and the trace context captured when the request insurance was created never reaches the wire.
RequestInsuranceBuilder::injectTraceHeaders()stores atraceparenton the row so the request can be tied back to the trace that created it.RequestPool::convertRequestToPromise()passes those stored headers toClient::requestAsync(), so the correct header is on the PSR-7 request.requestAsync()then calls the privateClient::transfer(), which is whatopen-telemetry/opentelemetry-auto-guzzlehooks. Its pre-hook removes every propagator field from the request:TraceContextPropagator::FIELDSistraceparentandtracestate, so the stored context is dropped. The hook then starts a client span withsetParent(Context::getCurrent())and injects that instead.Inside the worker the active context is the span around the chunk being processed, so every request in a chunk goes out as a child of that one span. The
RequestInsuranceInstrumentationbatch hook only callssetParent()when a chunk holds exactly one row; for larger chunks it adds links and lets the parent fall back to the ambient context. With the default chunk size of 100 that is effectively every chunk.Change
RequestPoolnow activates the trace context stored on the row for the duration of the send. The instrumentation'ssetParent(Context::getCurrent())then resolves per request, and the header it injects continues the originating trace rather than the worker's.Rows without a stored
traceparentare unaffected:TraceContextPropagator::extract()returns the current context unchanged when the header is missing or invalid, so the previous behaviour is preserved. The whole thing is skipped when the OpenTelemetry packages are not installed, matching the guard already used inRequestInsuranceBuilder.One consequence worth calling out: a row retried long after it was created now emits a
traceparentpointing at a span that has already finished. That is valid W3C propagation and is the same intent the existing links express, but it does mean retries attach to the original trace rather than starting a fresh one.