From 0ac59e1d120113987c0e4dd74000dc7212f12c57 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:27:51 +0000 Subject: [PATCH 1/3] fix(tracing): Propagate the span of the hub passed to the Guzzle middleware --- src/Tracing/GuzzleTracingMiddleware.php | 12 ++++++-- tests/Tracing/GuzzleTracingMiddlewareTest.php | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index fe34dda90..7768c6fc6 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -81,12 +81,20 @@ public static function trace(?HubInterface $hub = null): \Closure } if (self::shouldAttachTracingHeaders($sdkOptions, $request)) { - $traceParent = getTraceparent(); + // Propagate the span of the given hub, which is not necessarily the current hub + $span = $hub->getSpan(); + if ($span !== null && $sdkOptions !== null && $sdkOptions->isTracingEnabled()) { + $traceParent = $span->toTraceparent(); + $baggage = $span->toBaggage(); + } else { + $traceParent = getTraceparent(); + $baggage = getBaggage(); + } + if ($traceParent !== '') { $request = $request->withHeader('sentry-trace', $traceParent); } - $baggage = getBaggage(); if ($baggage !== '') { $request = $request->withHeader('baggage', $baggage); } diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index 86c37f10f..0d0322b8d 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -580,6 +580,36 @@ public function testTraceConcurrentRequests(): void $this->assertSame($transaction, $hub->getSpan()); } + public function testTraceHeadersUseTheSpanOfTheGivenHub(): void + { + $currentHubClient = $this->createMock(ClientInterface::class); + $currentHubClient->method('getOptions')->willReturn(new Options(['traces_sample_rate' => 1])); + $currentHub = new Hub($currentHubClient); + SentrySdk::setCurrentHub($currentHub); + $currentHub->setSpan($currentHub->startTransaction(new TransactionContext())); + + $client = $this->createMock(ClientInterface::class); + $client->method('getOptions')->willReturn(new Options(['traces_sample_rate' => 1])); + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + $hub->setSpan($transaction); + + $sentRequests = []; + $function = (GuzzleTracingMiddleware::trace($hub))(static function (Request $request) use ($hub, &$sentRequests): PromiseInterface { + $sentRequests[] = [$request, $hub->getSpan()]; + + return new FulfilledPromise(new Response()); + }); + + $function(new Request('GET', 'https://www.example.com'), []); + + [$request, $span] = $sentRequests[0]; + + $this->assertNotSame($transaction, $span); + $this->assertSame($span->toTraceparent(), $request->getHeaderLine('sentry-trace')); + $this->assertSame($span->toBaggage(), $request->getHeaderLine('baggage')); + } + public function testTraceRestoresTheCurrentSpanWhenTheHandlerThrows(): void { $client = $this->createMock(ClientInterface::class); From c8a01c8f99f61dc10f9342feb091bd89cfb99b82 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:19:50 +0000 Subject: [PATCH 2/3] fix(tracing): Accept a hub in getTraceparent() and getBaggage() Pass the Guzzle middleware hub to both functions instead of reading the span directly, and use the given hub consistently inside them. --- src/Tracing/GuzzleTracingMiddleware.php | 12 ++---------- src/functions.php | 12 ++++++------ tests/FunctionsTest.php | 21 +++++++++++++++++++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 7768c6fc6..745af61b5 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -81,20 +81,12 @@ public static function trace(?HubInterface $hub = null): \Closure } if (self::shouldAttachTracingHeaders($sdkOptions, $request)) { - // Propagate the span of the given hub, which is not necessarily the current hub - $span = $hub->getSpan(); - if ($span !== null && $sdkOptions !== null && $sdkOptions->isTracingEnabled()) { - $traceParent = $span->toTraceparent(); - $baggage = $span->toBaggage(); - } else { - $traceParent = getTraceparent(); - $baggage = getBaggage(); - } - + $traceParent = getTraceparent($hub); if ($traceParent !== '') { $request = $request->withHeader('sentry-trace', $traceParent); } + $baggage = getBaggage($hub); if ($baggage !== '') { $request = $request->withHeader('baggage', $baggage); } diff --git a/src/functions.php b/src/functions.php index 08324457d..126337015 100644 --- a/src/functions.php +++ b/src/functions.php @@ -382,16 +382,16 @@ function getOtlpTracesEndpointUrl(): ?string * This function is context aware, as in it either returns the traceparent based * on the current span, or the scope's propagation context. */ -function getTraceparent(): string +function getTraceparent(?HubInterface $hub = null): string { - $hub = SentrySdk::getCurrentHub(); + $hub = $hub ?? SentrySdk::getCurrentHub(); $client = $hub->getClient(); if ($client !== null) { $options = $client->getOptions(); if ($options->isTracingEnabled()) { - $span = SentrySdk::getCurrentHub()->getSpan(); + $span = $hub->getSpan(); if ($span !== null) { return $span->toTraceparent(); } @@ -429,16 +429,16 @@ function getW3CTraceparent(): string * This function is context aware, as in it either returns the baggage based * on the current span or the scope's propagation context. */ -function getBaggage(): string +function getBaggage(?HubInterface $hub = null): string { - $hub = SentrySdk::getCurrentHub(); + $hub = $hub ?? SentrySdk::getCurrentHub(); $client = $hub->getClient(); if ($client !== null) { $options = $client->getOptions(); if ($options->isTracingEnabled()) { - $span = SentrySdk::getCurrentHub()->getSpan(); + $span = $hub->getSpan(); if ($span !== null) { return $span->toBaggage(); } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index d5f9e64b4..93bf7bafe 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -683,6 +683,27 @@ public function testBaggageWithTracingEnabled(): void $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1,sentry-transaction=Test,sentry-release=1.0.0,sentry-environment=development,sentry-sampled=true,sentry-sample_rand=0.25', $baggage); } + public function testTraceHeadersUseTheGivenHub(): void + { + $currentHubClient = $this->createMock(ClientInterface::class); + $currentHubClient->method('getOptions')->willReturn(new Options(['traces_sample_rate' => 1.0])); + + $currentHub = new Hub($currentHubClient); + $currentHub->setSpan($currentHub->startTransaction(new TransactionContext())); + + SentrySdk::setCurrentHub($currentHub); + + $client = $this->createMock(ClientInterface::class); + $client->method('getOptions')->willReturn(new Options(['traces_sample_rate' => 1.0])); + + $hub = new Hub($client); + $transaction = $hub->startTransaction(new TransactionContext()); + $hub->setSpan($transaction); + + $this->assertSame($transaction->toTraceparent(), getTraceparent($hub)); + $this->assertSame($transaction->toBaggage(), getBaggage($hub)); + } + public function testGetOtlpTracesEndpointUrlFallsBackToDsn(): void { $client = $this->createMock(ClientInterface::class); From fe8335efde19b3ab8e35d7401758fe700224d451 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:17:02 +0000 Subject: [PATCH 3/3] fix(tracing): Use the given hub for the baggage fallback When the given hub has no span, getBaggage() builds the header from the scope's propagation context, which read the current hub's options. Pass the hub to PropagationContext::toBaggage() so the fallback uses it too. --- src/Tracing/PropagationContext.php | 5 +++-- src/functions.php | 4 ++-- tests/FunctionsTest.php | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index 1162484e1..bc1a06a99 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -5,6 +5,7 @@ namespace Sentry\Tracing; use Sentry\SentrySdk; +use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Tracing\Traits\TraceHeaderParserTrait; @@ -91,10 +92,10 @@ public function toW3CTraceparent(): string /** * Returns a string that can be used for the `baggage` header & meta tag. */ - public function toBaggage(): string + public function toBaggage(?HubInterface $hub = null): string { if ($this->dynamicSamplingContext === null) { - $hub = SentrySdk::getCurrentHub(); + $hub = $hub ?? SentrySdk::getCurrentHub(); $client = $hub->getClient(); if ($client !== null) { diff --git a/src/functions.php b/src/functions.php index c2cce04a1..b8c5ecbd7 100644 --- a/src/functions.php +++ b/src/functions.php @@ -447,12 +447,12 @@ function getBaggage(?HubInterface $hub = null): string } $baggage = ''; - $hub->configureScope(static function (Scope $scope) use (&$baggage) { + $hub->configureScope(static function (Scope $scope) use (&$baggage, $hub) { if ($scope->hasExternalPropagationContext()) { return; } - $baggage = $scope->getPropagationContext()->toBaggage(); + $baggage = $scope->getPropagationContext()->toBaggage($hub); }); return $baggage; diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 93bf7bafe..6baae08ba 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -704,6 +704,24 @@ public function testTraceHeadersUseTheGivenHub(): void $this->assertSame($transaction->toBaggage(), getBaggage($hub)); } + public function testBaggageUsesTheGivenHubWithoutASpan(): void + { + $currentHubClient = $this->createMock(ClientInterface::class); + $currentHubClient->method('getOptions')->willReturn(new Options(['release' => 'current-hub-release'])); + + SentrySdk::setCurrentHub(new Hub($currentHubClient)); + + $client = $this->createMock(ClientInterface::class); + $client->method('getOptions')->willReturn(new Options(['release' => 'given-hub-release'])); + + $hub = new Hub($client); + + $baggage = getBaggage($hub); + + $this->assertStringContainsString('sentry-release=given-hub-release', $baggage); + $this->assertStringNotContainsString('current-hub-release', $baggage); + } + public function testGetOtlpTracesEndpointUrlFallsBackToDsn(): void { $client = $this->createMock(ClientInterface::class);