Skip to content

Commit a31d418

Browse files
authored
Add support for W3C traceparent header (#1680)
1 parent 62274fb commit a31d418

9 files changed

+188
-8
lines changed

src/Tracing/GuzzleTracingMiddleware.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use function Sentry\getBaggage;
1717
use function Sentry\getTraceparent;
18+
use function Sentry\getW3CTraceparent;
1819

1920
/**
2021
* This handler traces each outgoing HTTP request by recording performance data.
@@ -33,6 +34,7 @@ public static function trace(?HubInterface $hub = null): \Closure
3334
if (self::shouldAttachTracingHeaders($client, $request)) {
3435
$request = $request
3536
->withHeader('sentry-trace', getTraceparent())
37+
->withHeader('traceparent', getW3CTraceparent())
3638
->withHeader('baggage', getBaggage());
3739
}
3840

@@ -60,6 +62,7 @@ public static function trace(?HubInterface $hub = null): \Closure
6062
if (self::shouldAttachTracingHeaders($client, $request)) {
6163
$request = $request
6264
->withHeader('sentry-trace', $childSpan->toTraceparent())
65+
->withHeader('traceparent', $childSpan->toW3CTraceparent())
6366
->withHeader('baggage', $childSpan->toBaggage());
6467
}
6568

src/Tracing/PropagationContext.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
final class PropagationContext
1111
{
12-
private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
12+
private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
13+
14+
private const W3C_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<version>[0]{2})?-?(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01]{2})?[ \\t]*$/i';
1315

1416
/**
1517
* @var TraceId The trace id
@@ -49,12 +51,12 @@ public static function fromDefaults(): self
4951

5052
public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self
5153
{
52-
return self::parseTraceAndBaggage($sentryTraceHeader, $baggageHeader);
54+
return self::parseTraceparentAndBaggage($sentryTraceHeader, $baggageHeader);
5355
}
5456

5557
public static function fromEnvironment(string $sentryTrace, string $baggage): self
5658
{
57-
return self::parseTraceAndBaggage($sentryTrace, $baggage);
59+
return self::parseTraceparentAndBaggage($sentryTrace, $baggage);
5860
}
5961

6062
/**
@@ -65,6 +67,14 @@ public function toTraceparent(): string
6567
return sprintf('%s-%s', (string) $this->traceId, (string) $this->spanId);
6668
}
6769

70+
/**
71+
* Returns a string that can be used for the W3C `traceparent` header & meta tag.
72+
*/
73+
public function toW3CTraceparent(): string
74+
{
75+
return sprintf('00-%s-%s', (string) $this->traceId, (string) $this->spanId);
76+
}
77+
6878
/**
6979
* Returns a string that can be used for the `baggage` header & meta tag.
7080
*/
@@ -149,12 +159,22 @@ public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplin
149159
return $this;
150160
}
151161

152-
private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self
162+
private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self
153163
{
154164
$context = self::fromDefaults();
155165
$hasSentryTrace = false;
156166

157-
if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
167+
if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) {
168+
if (!empty($matches['trace_id'])) {
169+
$context->traceId = new TraceId($matches['trace_id']);
170+
$hasSentryTrace = true;
171+
}
172+
173+
if (!empty($matches['span_id'])) {
174+
$context->parentSpanId = new SpanId($matches['span_id']);
175+
$hasSentryTrace = true;
176+
}
177+
} elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) {
158178
if (!empty($matches['trace_id'])) {
159179
$context->traceId = new TraceId($matches['trace_id']);
160180
$hasSentryTrace = true;

src/Tracing/Span.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,20 @@ public function toTraceparent(): string
567567
return sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled);
568568
}
569569

570+
/**
571+
* Returns a string that can be used for the W3C `traceparent` header & meta tag.
572+
*/
573+
public function toW3CTraceparent(): string
574+
{
575+
$sampled = '';
576+
577+
if ($this->sampled !== null) {
578+
$sampled = $this->sampled ? '-01' : '-00';
579+
}
580+
581+
return sprintf('00-%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled);
582+
}
583+
570584
/**
571585
* Returns a string that can be used for the `baggage` header & meta tag.
572586
*/

src/Tracing/TransactionContext.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
final class TransactionContext extends SpanContext
88
{
9-
private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
9+
private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
10+
11+
private const W3C_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<version>[0]{2})?-?(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01]{2})?[ \\t]*$/i';
1012

1113
public const DEFAULT_NAME = '<unlabeled transaction>';
1214

@@ -149,7 +151,7 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag
149151
$context = new self();
150152
$hasSentryTrace = false;
151153

152-
if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
154+
if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
153155
if (!empty($matches['trace_id'])) {
154156
$context->traceId = new TraceId($matches['trace_id']);
155157
$hasSentryTrace = true;
@@ -164,6 +166,21 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag
164166
$context->parentSampled = $matches['sampled'] === '1';
165167
$hasSentryTrace = true;
166168
}
169+
} elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
170+
if (!empty($matches['trace_id'])) {
171+
$context->traceId = new TraceId($matches['trace_id']);
172+
$hasSentryTrace = true;
173+
}
174+
175+
if (!empty($matches['span_id'])) {
176+
$context->parentSpanId = new SpanId($matches['span_id']);
177+
$hasSentryTrace = true;
178+
}
179+
180+
if (isset($matches['sampled'])) {
181+
$context->parentSampled = $matches['sampled'] === '01';
182+
$hasSentryTrace = true;
183+
}
167184
}
168185

169186
$samplingContext = DynamicSamplingContext::fromHeader($baggage);

src/functions.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ function trace(callable $trace, SpanContext $context)
224224
}
225225

226226
/**
227-
* Creates the current traceparent string, to be used as a HTTP header value
227+
* Creates the current Sentry traceparent string, to be used as a HTTP header value
228228
* or HTML meta tag value.
229229
* This function is context aware, as in it either returns the traceparent based
230230
* on the current span, or the scope's propagation context.
@@ -253,6 +253,36 @@ function getTraceparent(): string
253253
return $traceParent;
254254
}
255255

256+
/**
257+
* Creates the current W3C traceparent string, to be used as a HTTP header value
258+
* or HTML meta tag value.
259+
* This function is context aware, as in it either returns the traceparent based
260+
* on the current span, or the scope's propagation context.
261+
*/
262+
function getW3CTraceparent(): string
263+
{
264+
$hub = SentrySdk::getCurrentHub();
265+
$client = $hub->getClient();
266+
267+
if ($client !== null) {
268+
$options = $client->getOptions();
269+
270+
if ($options !== null && $options->isTracingEnabled()) {
271+
$span = SentrySdk::getCurrentHub()->getSpan();
272+
if ($span !== null) {
273+
return $span->toW3CTraceparent();
274+
}
275+
}
276+
}
277+
278+
$traceParent = '';
279+
$hub->configureScope(function (Scope $scope) use (&$traceParent) {
280+
$traceParent = $scope->getPropagationContext()->toW3CTraceparent();
281+
});
282+
283+
return $traceParent;
284+
}
285+
256286
/**
257287
* Creates the baggage content string, to be used as a HTTP header value
258288
* or HTML meta tag value.

tests/FunctionsTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use function Sentry\continueTrace;
4040
use function Sentry\getBaggage;
4141
use function Sentry\getTraceparent;
42+
use function Sentry\getW3CTraceparent;
4243
use function Sentry\init;
4344
use function Sentry\startTransaction;
4445
use function Sentry\trace;
@@ -429,6 +430,49 @@ public function testTraceparentWithTracingEnabled(): void
429430
$this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent);
430431
}
431432

433+
public function testW3CTraceparentWithTracingDisabled(): void
434+
{
435+
$propagationContext = PropagationContext::fromDefaults();
436+
$propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19'));
437+
$propagationContext->setSpanId(new SpanId('566e3688a61d4bc8'));
438+
439+
$scope = new Scope($propagationContext);
440+
441+
$hub = new Hub(null, $scope);
442+
443+
SentrySdk::setCurrentHub($hub);
444+
445+
$traceParent = getW3CTraceparent();
446+
447+
$this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent);
448+
}
449+
450+
public function testW3CTraceparentWithTracingEnabled(): void
451+
{
452+
$client = $this->createMock(ClientInterface::class);
453+
$client->expects($this->once())
454+
->method('getOptions')
455+
->willReturn(new Options([
456+
'traces_sample_rate' => 1.0,
457+
]));
458+
459+
$hub = new Hub($client);
460+
461+
SentrySdk::setCurrentHub($hub);
462+
463+
$spanContext = (new SpanContext())
464+
->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19'))
465+
->setSpanId(new SpanId('566e3688a61d4bc8'));
466+
467+
$span = new Span($spanContext);
468+
469+
$hub->setSpan($span);
470+
471+
$traceParent = getW3CTraceparent();
472+
473+
$this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent);
474+
}
475+
432476
public function testBaggageWithTracingDisabled(): void
433477
{
434478
$propagationContext = PropagationContext::fromDefaults();

tests/Tracing/GuzzleTracingMiddlewareTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade
7777
$function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface {
7878
if ($headersShouldBePresent) {
7979
$this->assertNotEmpty($request->getHeader('sentry-trace'));
80+
$this->assertNotEmpty($request->getHeader('traceparent'));
8081
$this->assertNotEmpty($request->getHeader('baggage'));
8182
} else {
8283
$this->assertEmpty($request->getHeader('sentry-trace'));
84+
$this->assertEmpty($request->getHeader('traceparent'));
8385
$this->assertEmpty($request->getHeader('baggage'));
8486
}
8587

@@ -112,9 +114,11 @@ public function testTraceHeadersWithTransacttion(Request $request, Options $opti
112114
$function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface {
113115
if ($headersShouldBePresent) {
114116
$this->assertNotEmpty($request->getHeader('sentry-trace'));
117+
$this->assertNotEmpty($request->getHeader('traceparent'));
115118
$this->assertNotEmpty($request->getHeader('baggage'));
116119
} else {
117120
$this->assertEmpty($request->getHeader('sentry-trace'));
121+
$this->assertEmpty($request->getHeader('traceparent'));
118122
$this->assertEmpty($request->getHeader('baggage'));
119123
}
120124

@@ -241,6 +245,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec
241245
$middleware = GuzzleTracingMiddleware::trace($hub);
242246
$function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface {
243247
$this->assertNotEmpty($request->getHeader('sentry-trace'));
248+
$this->assertNotEmpty($request->getHeader('traceparent'));
244249
$this->assertNotEmpty($request->getHeader('baggage'));
245250
if ($expectedPromiseResult instanceof \Throwable) {
246251
return new RejectedPromise($expectedPromiseResult);

tests/Tracing/PropagationContextTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ public static function tracingDataProvider(): iterable
7878
true,
7979
];
8080

81+
yield [
82+
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01',
83+
'',
84+
new TraceId('566e3688a61d4bc888951642d6f14a19'),
85+
new SpanId('566e3688a61d4bc8'),
86+
true,
87+
];
88+
8189
yield [
8290
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1',
8391
'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1',
@@ -104,6 +112,15 @@ public function testToTraceparent()
104112
$this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toTraceparent());
105113
}
106114

115+
public function testToW3CTraceparent()
116+
{
117+
$propagationContext = PropagationContext::fromDefaults();
118+
$propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19'));
119+
$propagationContext->setSpanId(new SpanId('566e3688a61d4bc8'));
120+
121+
$this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toW3CTraceparent());
122+
}
123+
107124
public function testToBaggage()
108125
{
109126
$dynamicSamplingContext = DynamicSamplingContext::fromHeader('sentry-trace_id=566e3688a61d4bc888951642d6f14a19');

tests/Tracing/TransactionContextTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,36 @@ public static function tracingDataProvider(): iterable
117117
true,
118118
];
119119

120+
yield [
121+
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00',
122+
'',
123+
new SpanId('566e3688a61d4bc8'),
124+
new TraceId('566e3688a61d4bc888951642d6f14a19'),
125+
false,
126+
DynamicSamplingContext::class,
127+
true,
128+
];
129+
130+
yield [
131+
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01',
132+
'',
133+
new SpanId('566e3688a61d4bc8'),
134+
new TraceId('566e3688a61d4bc888951642d6f14a19'),
135+
true,
136+
DynamicSamplingContext::class,
137+
true,
138+
];
139+
140+
yield [
141+
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8',
142+
'',
143+
new SpanId('566e3688a61d4bc8'),
144+
new TraceId('566e3688a61d4bc888951642d6f14a19'),
145+
null,
146+
DynamicSamplingContext::class,
147+
true,
148+
];
149+
120150
yield [
121151
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1',
122152
'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1',

0 commit comments

Comments
 (0)