Skip to content

Commit a9ada24

Browse files
authored
Refactor trace header parsing logic (#1876)
1 parent aff42b5 commit a9ada24

4 files changed

Lines changed: 143 additions & 91 deletions

File tree

phpstan-baseline.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ parameters:
310310
count: 1
311311
path: src/Tracing/GuzzleTracingMiddleware.php
312312

313+
-
314+
message: "#^Property Sentry\\\\Tracing\\\\PropagationContext\\:\\:\\$parentSampled is never read, only written\\.$#"
315+
count: 1
316+
path: src/Tracing/PropagationContext.php
317+
313318
-
314319
message: "#^Method Sentry\\\\Tracing\\\\Span\\:\\:getMetricsSummary\\(\\) return type has no value type specified in iterable type array\\.$#"
315320
count: 1

src/Tracing/PropagationContext.php

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
use Sentry\SentrySdk;
88
use Sentry\State\Scope;
9+
use Sentry\Tracing\Traits\TraceHeaderParserTrait;
910

1011
final class PropagationContext
1112
{
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+
use TraceHeaderParserTrait;
1314

1415
/**
1516
* @var TraceId The trace id
@@ -183,60 +184,29 @@ public function setSampleRand(?float $sampleRand): self
183184
return $this;
184185
}
185186

186-
// TODO add same logic as in TransactionContext
187187
private static function parseTraceparentAndBaggage(string $traceparent, string $baggage): self
188188
{
189189
$context = self::fromDefaults();
190-
$hasSentryTrace = false;
190+
$parsedData = self::parseTraceAndBaggageHeaders($traceparent, $baggage);
191191

192-
if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $traceparent, $matches)) {
193-
if (!empty($matches['trace_id'])) {
194-
$context->traceId = new TraceId($matches['trace_id']);
195-
$hasSentryTrace = true;
196-
}
197-
198-
if (!empty($matches['span_id'])) {
199-
$context->parentSpanId = new SpanId($matches['span_id']);
200-
$hasSentryTrace = true;
201-
}
202-
203-
if (isset($matches['sampled'])) {
204-
$context->parentSampled = $matches['sampled'] === '1';
205-
$hasSentryTrace = true;
206-
}
192+
if ($parsedData['traceId'] !== null) {
193+
$context->traceId = $parsedData['traceId'];
207194
}
208195

209-
$samplingContext = DynamicSamplingContext::fromHeader($baggage);
196+
if ($parsedData['parentSpanId'] !== null) {
197+
$context->parentSpanId = $parsedData['parentSpanId'];
198+
}
210199

211-
if ($hasSentryTrace && !$samplingContext->hasEntries()) {
212-
// The request comes from an old SDK which does not support Dynamic Sampling.
213-
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
214-
$samplingContext->freeze();
215-
$context->dynamicSamplingContext = $samplingContext;
200+
if ($parsedData['parentSampled'] !== null) {
201+
$context->parentSampled = $parsedData['parentSampled'];
216202
}
217203

218-
if ($hasSentryTrace && $samplingContext->hasEntries()) {
219-
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
220-
// Propagate this Dynamic Sampling Context.
221-
$context->dynamicSamplingContext = $samplingContext;
204+
if ($parsedData['dynamicSamplingContext'] !== null) {
205+
$context->dynamicSamplingContext = $parsedData['dynamicSamplingContext'];
222206
}
223207

224-
// Store the propagated trace sample rand or generate a new one
225-
if ($samplingContext->has('sample_rand')) {
226-
$context->sampleRand = (float) $samplingContext->get('sample_rand');
227-
} else {
228-
if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) {
229-
if ($context->parentSampled === true) {
230-
// [0, rate)
231-
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6);
232-
} else {
233-
// [rate, 1)
234-
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6);
235-
}
236-
} elseif ($context->parentSampled !== null) {
237-
// [0, 1)
238-
$context->sampleRand = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
239-
}
208+
if ($parsedData['sampleRand'] !== null) {
209+
$context->sampleRand = $parsedData['sampleRand'];
240210
}
241211

242212
return $context;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sentry\Tracing\Traits;
6+
7+
use Sentry\Tracing\DynamicSamplingContext;
8+
use Sentry\Tracing\SpanId;
9+
use Sentry\Tracing\TraceId;
10+
11+
/**
12+
* @internal
13+
*/
14+
trait TraceHeaderParserTrait
15+
{
16+
/**
17+
* @var string The regex for parsing the sentry-trace header
18+
*/
19+
private static $sentryTraceparentHeaderRegex = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
20+
21+
/**
22+
* Parses the sentry-trace and baggage headers and returns the extracted data.
23+
*
24+
* @param string $sentryTrace The sentry-trace header value
25+
* @param string $baggage The baggage header value
26+
*
27+
* @return array{
28+
* traceId: TraceId|null,
29+
* parentSpanId: SpanId|null,
30+
* parentSampled: bool|null,
31+
* dynamicSamplingContext: DynamicSamplingContext|null,
32+
* sampleRand: float|null,
33+
* parentSamplingRate: float|null
34+
* }
35+
*/
36+
protected static function parseTraceAndBaggageHeaders(string $sentryTrace, string $baggage): array
37+
{
38+
$result = [
39+
'traceId' => null,
40+
'parentSpanId' => null,
41+
'parentSampled' => null,
42+
'dynamicSamplingContext' => null,
43+
'sampleRand' => null,
44+
'parentSamplingRate' => null,
45+
];
46+
47+
$hasSentryTrace = false;
48+
49+
if (preg_match(self::$sentryTraceparentHeaderRegex, $sentryTrace, $matches)) {
50+
if (!empty($matches['trace_id'])) {
51+
$result['traceId'] = new TraceId($matches['trace_id']);
52+
$hasSentryTrace = true;
53+
}
54+
55+
if (!empty($matches['span_id'])) {
56+
$result['parentSpanId'] = new SpanId($matches['span_id']);
57+
$hasSentryTrace = true;
58+
}
59+
60+
if (isset($matches['sampled'])) {
61+
$result['parentSampled'] = $matches['sampled'] === '1';
62+
$hasSentryTrace = true;
63+
}
64+
}
65+
66+
$samplingContext = DynamicSamplingContext::fromHeader($baggage);
67+
68+
if ($hasSentryTrace && !$samplingContext->hasEntries()) {
69+
// The request comes from an old SDK which does not support Dynamic Sampling.
70+
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
71+
$samplingContext->freeze();
72+
$result['dynamicSamplingContext'] = $samplingContext;
73+
}
74+
75+
if ($hasSentryTrace && $samplingContext->hasEntries()) {
76+
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
77+
// Propagate this Dynamic Sampling Context.
78+
$result['dynamicSamplingContext'] = $samplingContext;
79+
}
80+
81+
// Store the propagated traces sample rate
82+
if ($samplingContext->has('sample_rate')) {
83+
$result['parentSamplingRate'] = (float) $samplingContext->get('sample_rate');
84+
}
85+
86+
// Store the propagated trace sample rand or generate a new one
87+
if ($samplingContext->has('sample_rand')) {
88+
$result['sampleRand'] = (float) $samplingContext->get('sample_rand');
89+
} else {
90+
if ($samplingContext->has('sample_rate') && $result['parentSampled'] !== null) {
91+
if ($result['parentSampled'] === true) {
92+
// [0, rate)
93+
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6);
94+
} else {
95+
// [rate, 1)
96+
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6);
97+
}
98+
} elseif ($result['parentSampled'] !== null) {
99+
// [0, 1)
100+
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6);
101+
}
102+
}
103+
104+
return $result;
105+
}
106+
}

src/Tracing/TransactionContext.php

Lines changed: 18 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
namespace Sentry\Tracing;
66

7+
use Sentry\Tracing\Traits\TraceHeaderParserTrait;
8+
79
final class TransactionContext extends SpanContext
810
{
9-
private const SENTRY_TRACEPARENT_HEADER_REGEX = '/^[ \\t]*(?<trace_id>[0-9a-f]{32})?-?(?<span_id>[0-9a-f]{16})?-?(?<sampled>[01])?[ \\t]*$/i';
11+
use TraceHeaderParserTrait;
1012

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

@@ -147,61 +149,30 @@ public static function fromHeaders(string $sentryTraceHeader, string $baggageHea
147149
private static function parseTraceAndBaggage(string $sentryTrace, string $baggage): self
148150
{
149151
$context = new self();
150-
$hasSentryTrace = false;
151-
152-
if (preg_match(self::SENTRY_TRACEPARENT_HEADER_REGEX, $sentryTrace, $matches)) {
153-
if (!empty($matches['trace_id'])) {
154-
$context->traceId = new TraceId($matches['trace_id']);
155-
$hasSentryTrace = true;
156-
}
157-
158-
if (!empty($matches['span_id'])) {
159-
$context->parentSpanId = new SpanId($matches['span_id']);
160-
$hasSentryTrace = true;
161-
}
162-
163-
if (isset($matches['sampled'])) {
164-
$context->parentSampled = $matches['sampled'] === '1';
165-
$hasSentryTrace = true;
166-
}
152+
$parsedData = self::parseTraceAndBaggageHeaders($sentryTrace, $baggage);
153+
154+
if ($parsedData['traceId'] !== null) {
155+
$context->traceId = $parsedData['traceId'];
167156
}
168157

169-
$samplingContext = DynamicSamplingContext::fromHeader($baggage);
158+
if ($parsedData['parentSpanId'] !== null) {
159+
$context->parentSpanId = $parsedData['parentSpanId'];
160+
}
170161

171-
if ($hasSentryTrace && !$samplingContext->hasEntries()) {
172-
// The request comes from an old SDK which does not support Dynamic Sampling.
173-
// Propagate the Dynamic Sampling Context as is, but frozen, even without sentry-* entries.
174-
$samplingContext->freeze();
175-
$context->getMetadata()->setDynamicSamplingContext($samplingContext);
162+
if ($parsedData['parentSampled'] !== null) {
163+
$context->parentSampled = $parsedData['parentSampled'];
176164
}
177165

178-
if ($hasSentryTrace && $samplingContext->hasEntries()) {
179-
// The baggage header contains Dynamic Sampling Context data from an upstream SDK.
180-
// Propagate this Dynamic Sampling Context.
181-
$context->getMetadata()->setDynamicSamplingContext($samplingContext);
166+
if ($parsedData['dynamicSamplingContext'] !== null) {
167+
$context->getMetadata()->setDynamicSamplingContext($parsedData['dynamicSamplingContext']);
182168
}
183169

184-
// Store the propagated traces sample rate
185-
if ($samplingContext->has('sample_rate')) {
186-
$context->getMetadata()->setParentSamplingRate((float) $samplingContext->get('sample_rate'));
170+
if ($parsedData['parentSamplingRate'] !== null) {
171+
$context->getMetadata()->setParentSamplingRate($parsedData['parentSamplingRate']);
187172
}
188173

189-
// Store the propagated trace sample rand or generate a new one
190-
if ($samplingContext->has('sample_rand')) {
191-
$context->getMetadata()->setSampleRand((float) $samplingContext->get('sample_rand'));
192-
} else {
193-
if ($samplingContext->has('sample_rate') && $context->parentSampled !== null) {
194-
if ($context->parentSampled === true) {
195-
// [0, rate)
196-
$context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6));
197-
} else {
198-
// [rate, 1)
199-
$context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (1 - (float) $samplingContext->get('sample_rate')) + (float) $samplingContext->get('sample_rate'), 6));
200-
}
201-
} elseif ($context->parentSampled !== null) {
202-
// [0, 1)
203-
$context->getMetadata()->setSampleRand(round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax(), 6));
204-
}
174+
if ($parsedData['sampleRand'] !== null) {
175+
$context->getMetadata()->setSampleRand($parsedData['sampleRand']);
205176
}
206177

207178
return $context;

0 commit comments

Comments
 (0)