Skip to content

Commit

Permalink
Add support for W3C traceparent header (#1680)
Browse files Browse the repository at this point in the history
  • Loading branch information
cleptric authored Jan 18, 2024
1 parent 62274fb commit a31d418
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 8 deletions.
3 changes: 3 additions & 0 deletions src/Tracing/GuzzleTracingMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use function Sentry\getBaggage;
use function Sentry\getTraceparent;
use function Sentry\getW3CTraceparent;

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

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

Expand Down
30 changes: 25 additions & 5 deletions src/Tracing/PropagationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

final class PropagationContext
{
private const TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';

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';

/**
* @var TraceId The trace id
Expand Down Expand Up @@ -49,12 +51,12 @@ public static function fromDefaults(): self

public static function fromHeaders(string $sentryTraceHeader, string $baggageHeader): self
{
return self::parseTraceAndBaggage($sentryTraceHeader, $baggageHeader);
return self::parseTraceparentAndBaggage($sentryTraceHeader, $baggageHeader);
}

public static function fromEnvironment(string $sentryTrace, string $baggage): self
{
return self::parseTraceAndBaggage($sentryTrace, $baggage);
return self::parseTraceparentAndBaggage($sentryTrace, $baggage);
}

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

/**
* Returns a string that can be used for the W3C `traceparent` header & meta tag.
*/
public function toW3CTraceparent(): string
{
return sprintf('00-%s-%s', (string) $this->traceId, (string) $this->spanId);
}

/**
* Returns a string that can be used for the `baggage` header & meta tag.
*/
Expand Down Expand Up @@ -149,12 +159,22 @@ public function setDynamicSamplingContext(DynamicSamplingContext $dynamicSamplin
return $this;
}

private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self
private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self
{
$context = self::fromDefaults();
$hasSentryTrace = false;

if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) {
if (!empty($matches['trace_id'])) {
$context->traceId = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
}

if (!empty($matches['span_id'])) {
$context->parentSpanId = new SpanId($matches['span_id']);
$hasSentryTrace = true;
}
} elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) {
if (!empty($matches['trace_id'])) {
$context->traceId = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
Expand Down
14 changes: 14 additions & 0 deletions src/Tracing/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,20 @@ public function toTraceparent(): string
return sprintf('%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled);
}

/**
* Returns a string that can be used for the W3C `traceparent` header & meta tag.
*/
public function toW3CTraceparent(): string
{
$sampled = '';

if ($this->sampled !== null) {
$sampled = $this->sampled ? '-01' : '-00';
}

return sprintf('00-%s-%s%s', (string) $this->traceId, (string) $this->spanId, $sampled);
}

/**
* Returns a string that can be used for the `baggage` header & meta tag.
*/
Expand Down
21 changes: 19 additions & 2 deletions src/Tracing/TransactionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

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

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';

public const DEFAULT_NAME = '<unlabeled transaction>';

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

if (preg_match(self::TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
if (!empty($matches['trace_id'])) {
$context->traceId = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
Expand All @@ -164,6 +166,21 @@ private static function parseTraceAndBaggage(string $sentryTrace, string $baggag
$context->parentSampled = $matches['sampled'] === '1';
$hasSentryTrace = true;
}
} elseif (preg_match(self::W3C_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
if (!empty($matches['trace_id'])) {
$context->traceId = new TraceId($matches['trace_id']);
$hasSentryTrace = true;
}

if (!empty($matches['span_id'])) {
$context->parentSpanId = new SpanId($matches['span_id']);
$hasSentryTrace = true;
}

if (isset($matches['sampled'])) {
$context->parentSampled = $matches['sampled'] === '01';
$hasSentryTrace = true;
}
}

$samplingContext = DynamicSamplingContext::fromHeader($baggage);
Expand Down
32 changes: 31 additions & 1 deletion src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function trace(callable $trace, SpanContext $context)
}

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

/**
* Creates the current W3C traceparent string, to be used as a HTTP header value
* or HTML meta tag value.
* This function is context aware, as in it either returns the traceparent based
* on the current span, or the scope's propagation context.
*/
function getW3CTraceparent(): string
{
$hub = SentrySdk::getCurrentHub();
$client = $hub->getClient();

if ($client !== null) {
$options = $client->getOptions();

if ($options !== null && $options->isTracingEnabled()) {
$span = SentrySdk::getCurrentHub()->getSpan();
if ($span !== null) {
return $span->toW3CTraceparent();
}
}
}

$traceParent = '';
$hub->configureScope(function (Scope $scope) use (&$traceParent) {
$traceParent = $scope->getPropagationContext()->toW3CTraceparent();
});

return $traceParent;
}

/**
* Creates the baggage content string, to be used as a HTTP header value
* or HTML meta tag value.
Expand Down
44 changes: 44 additions & 0 deletions tests/FunctionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use function Sentry\continueTrace;
use function Sentry\getBaggage;
use function Sentry\getTraceparent;
use function Sentry\getW3CTraceparent;
use function Sentry\init;
use function Sentry\startTransaction;
use function Sentry\trace;
Expand Down Expand Up @@ -429,6 +430,49 @@ public function testTraceparentWithTracingEnabled(): void
$this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent);
}

public function testW3CTraceparentWithTracingDisabled(): void
{
$propagationContext = PropagationContext::fromDefaults();
$propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19'));
$propagationContext->setSpanId(new SpanId('566e3688a61d4bc8'));

$scope = new Scope($propagationContext);

$hub = new Hub(null, $scope);

SentrySdk::setCurrentHub($hub);

$traceParent = getW3CTraceparent();

$this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent);
}

public function testW3CTraceparentWithTracingEnabled(): void
{
$client = $this->createMock(ClientInterface::class);
$client->expects($this->once())
->method('getOptions')
->willReturn(new Options([
'traces_sample_rate' => 1.0,
]));

$hub = new Hub($client);

SentrySdk::setCurrentHub($hub);

$spanContext = (new SpanContext())
->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19'))
->setSpanId(new SpanId('566e3688a61d4bc8'));

$span = new Span($spanContext);

$hub->setSpan($span);

$traceParent = getW3CTraceparent();

$this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $traceParent);
}

public function testBaggageWithTracingDisabled(): void
{
$propagationContext = PropagationContext::fromDefaults();
Expand Down
5 changes: 5 additions & 0 deletions tests/Tracing/GuzzleTracingMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade
$function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface {
if ($headersShouldBePresent) {
$this->assertNotEmpty($request->getHeader('sentry-trace'));
$this->assertNotEmpty($request->getHeader('traceparent'));
$this->assertNotEmpty($request->getHeader('baggage'));
} else {
$this->assertEmpty($request->getHeader('sentry-trace'));
$this->assertEmpty($request->getHeader('traceparent'));
$this->assertEmpty($request->getHeader('baggage'));
}

Expand Down Expand Up @@ -112,9 +114,11 @@ public function testTraceHeadersWithTransacttion(Request $request, Options $opti
$function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface {
if ($headersShouldBePresent) {
$this->assertNotEmpty($request->getHeader('sentry-trace'));
$this->assertNotEmpty($request->getHeader('traceparent'));
$this->assertNotEmpty($request->getHeader('baggage'));
} else {
$this->assertEmpty($request->getHeader('sentry-trace'));
$this->assertEmpty($request->getHeader('traceparent'));
$this->assertEmpty($request->getHeader('baggage'));
}

Expand Down Expand Up @@ -241,6 +245,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec
$middleware = GuzzleTracingMiddleware::trace($hub);
$function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface {
$this->assertNotEmpty($request->getHeader('sentry-trace'));
$this->assertNotEmpty($request->getHeader('traceparent'));
$this->assertNotEmpty($request->getHeader('baggage'));
if ($expectedPromiseResult instanceof \Throwable) {
return new RejectedPromise($expectedPromiseResult);
Expand Down
17 changes: 17 additions & 0 deletions tests/Tracing/PropagationContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ public static function tracingDataProvider(): iterable
true,
];

yield [
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01',
'',
new TraceId('566e3688a61d4bc888951642d6f14a19'),
new SpanId('566e3688a61d4bc8'),
true,
];

yield [
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1',
'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1',
Expand All @@ -104,6 +112,15 @@ public function testToTraceparent()
$this->assertSame('566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toTraceparent());
}

public function testToW3CTraceparent()
{
$propagationContext = PropagationContext::fromDefaults();
$propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19'));
$propagationContext->setSpanId(new SpanId('566e3688a61d4bc8'));

$this->assertSame('00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', $propagationContext->toW3CTraceparent());
}

public function testToBaggage()
{
$dynamicSamplingContext = DynamicSamplingContext::fromHeader('sentry-trace_id=566e3688a61d4bc888951642d6f14a19');
Expand Down
30 changes: 30 additions & 0 deletions tests/Tracing/TransactionContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,36 @@ public static function tracingDataProvider(): iterable
true,
];

yield [
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-00',
'',
new SpanId('566e3688a61d4bc8'),
new TraceId('566e3688a61d4bc888951642d6f14a19'),
false,
DynamicSamplingContext::class,
true,
];

yield [
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-01',
'',
new SpanId('566e3688a61d4bc8'),
new TraceId('566e3688a61d4bc888951642d6f14a19'),
true,
DynamicSamplingContext::class,
true,
];

yield [
'00-566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8',
'',
new SpanId('566e3688a61d4bc8'),
new TraceId('566e3688a61d4bc888951642d6f14a19'),
null,
DynamicSamplingContext::class,
true,
];

yield [
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1',
'sentry-public_key=public,sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rate=1',
Expand Down

0 comments on commit a31d418

Please sign in to comment.