Skip to content

Commit c9dd4ad

Browse files
authored
fix: invalid sample_rand is ignored (#2077)
1 parent 7098cf6 commit c9dd4ad

4 files changed

Lines changed: 143 additions & 4 deletions

File tree

src/Tracing/Traits/TraceHeaderParserTrait.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ protected static function parseTraceAndBaggageHeaders(string $sentryTrace, strin
9292
}
9393

9494
// Store the propagated trace sample rand or generate a new one
95-
if ($samplingContext->has('sample_rand')) {
96-
$result['sampleRand'] = (float) $samplingContext->get('sample_rand');
97-
} else {
98-
if ($samplingContext->has('sample_rate') && $result['parentSampled'] !== null) {
95+
if ($hasSentryTrace) {
96+
$incomingSampleRand = self::parseSampleRand($samplingContext);
97+
if ($incomingSampleRand !== null) {
98+
$result['sampleRand'] = $incomingSampleRand;
99+
} elseif ($samplingContext->has('sample_rate') && $result['parentSampled'] !== null) {
99100
if ($result['parentSampled'] === true) {
100101
// [0, rate)
101102
$result['sampleRand'] = round(mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() * (float) $samplingContext->get('sample_rate'), 6);
@@ -112,6 +113,32 @@ protected static function parseTraceAndBaggageHeaders(string $sentryTrace, strin
112113
return $result;
113114
}
114115

116+
private static function parseSampleRand(DynamicSamplingContext $samplingContext): ?float
117+
{
118+
$sampleRand = $samplingContext->get('sample_rand');
119+
if ($sampleRand === null) {
120+
return null;
121+
}
122+
123+
if (is_numeric($sampleRand)) {
124+
$sampleRandAsFloat = (float) $sampleRand;
125+
if ($sampleRandAsFloat >= 0.0 && $sampleRandAsFloat < 1.0) {
126+
return $sampleRandAsFloat;
127+
}
128+
}
129+
130+
$hub = SentrySdk::getCurrentHub();
131+
$client = $hub->getClient();
132+
if ($client !== null) {
133+
$client->getOptions()->getLoggerOrNullLogger()->debug(
134+
'Ignoring invalid sentry-sample_rand baggage value because it must be a numeric value in the range [0, 1).',
135+
['sample_rand' => $sampleRand]
136+
);
137+
}
138+
139+
return null;
140+
}
141+
115142
private static function shouldContinueTrace(DynamicSamplingContext $samplingContext): bool
116143
{
117144
$hub = SentrySdk::getCurrentHub();

tests/State/HubTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,17 @@ public static function startTransactionDataProvider(): iterable
760760
false,
761761
];
762762

763+
yield 'Invalid incoming sample_rand is ignored' => [
764+
new Options([
765+
'traces_sample_rate' => 1.0,
766+
]),
767+
TransactionContext::fromHeaders(
768+
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8',
769+
'sentry-sample_rand=2.0'
770+
),
771+
true,
772+
];
773+
763774
yield 'Out of range sample rate returned from traces_sampler (lower than minimum)' => [
764775
new Options([
765776
'traces_sampler' => static function (): float {

tests/Tracing/PropagationContextTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,41 @@ public function testGetTraceContext(): void
136136
], $propagationContext->getTraceContext());
137137
}
138138

139+
/**
140+
* @dataProvider invalidSampleRandDataProvider
141+
*/
142+
public function testInvalidSampleRandIsIgnored(string $sampleRand): void
143+
{
144+
$propagationContext = PropagationContext::fromHeaders(
145+
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1',
146+
'sentry-sample_rate=0.4,sentry-sample_rand=' . rawurlencode($sampleRand)
147+
);
148+
149+
$generatedSampleRand = $propagationContext->getSampleRand();
150+
151+
$this->assertNotNull($generatedSampleRand);
152+
$this->assertGreaterThanOrEqual(0.0, $generatedSampleRand);
153+
$this->assertLessThan(0.4, $generatedSampleRand);
154+
}
155+
156+
public function testSampleRandIsIgnoredWithoutSentryTraceHeader(): void
157+
{
158+
$propagationContext = PropagationContext::fromHeaders('', 'sentry-sample_rand=-1.0');
159+
$sampleRand = $propagationContext->getSampleRand();
160+
161+
$this->assertNotNull($sampleRand);
162+
$this->assertGreaterThanOrEqual(0.0, $sampleRand);
163+
$this->assertLessThanOrEqual(1.0, $sampleRand);
164+
}
165+
166+
public static function invalidSampleRandDataProvider(): iterable
167+
{
168+
yield ['-1.0'];
169+
yield ['1'];
170+
yield ['2.0'];
171+
yield ['foo'];
172+
}
173+
139174
public function testSampleRandRangeWhenParentNotSampledAndSampleRateProvided(): void
140175
{
141176
$propagationContext = PropagationContext::fromHeaders(

tests/Tracing/TransactionContextTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
namespace Sentry\Tests\Tracing;
66

77
use PHPUnit\Framework\TestCase;
8+
use Psr\Log\LoggerInterface;
9+
use Sentry\ClientInterface;
10+
use Sentry\Options;
11+
use Sentry\SentrySdk;
12+
use Sentry\State\Hub;
813
use Sentry\Tracing\DynamicSamplingContext;
914
use Sentry\Tracing\SpanId;
1015
use Sentry\Tracing\TraceId;
@@ -135,6 +140,67 @@ public static function tracingDataProvider(): iterable
135140
];
136141
}
137142

143+
/**
144+
* @dataProvider invalidSampleRandDataProvider
145+
*/
146+
public function testInvalidSampleRandIsIgnored(string $sampleRand): void
147+
{
148+
$context = TransactionContext::fromHeaders(
149+
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1',
150+
'sentry-sample_rate=0.4,sentry-sample_rand=' . rawurlencode($sampleRand)
151+
);
152+
153+
$generatedSampleRand = $context->getMetadata()->getSampleRand();
154+
155+
$this->assertNotNull($generatedSampleRand);
156+
$this->assertGreaterThanOrEqual(0.0, $generatedSampleRand);
157+
$this->assertLessThan(0.4, $generatedSampleRand);
158+
}
159+
160+
public function testSampleRandIsIgnoredWithoutSentryTraceHeader(): void
161+
{
162+
$context = TransactionContext::fromHeaders('', 'sentry-sample_rand=-1.0');
163+
$sampleRand = $context->getMetadata()->getSampleRand();
164+
165+
$this->assertNotNull($sampleRand);
166+
$this->assertGreaterThanOrEqual(0.0, $sampleRand);
167+
$this->assertLessThanOrEqual(1.0, $sampleRand);
168+
}
169+
170+
public function testInvalidSampleRandIsLogged(): void
171+
{
172+
$logger = $this->createMock(LoggerInterface::class);
173+
$logger->expects($this->once())
174+
->method('debug')
175+
->with(
176+
$this->stringContains('Ignoring invalid sentry-sample_rand baggage value'),
177+
['sample_rand' => '-1.0']
178+
);
179+
180+
$client = $this->createMock(ClientInterface::class);
181+
$client->method('getOptions')
182+
->willReturn(new Options(['logger' => $logger]));
183+
184+
SentrySdk::setCurrentHub(new Hub($client));
185+
186+
try {
187+
TransactionContext::fromHeaders(
188+
'566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8',
189+
'sentry-sample_rand=-1.0'
190+
);
191+
} finally {
192+
SentrySdk::setCurrentHub(new Hub());
193+
}
194+
}
195+
196+
public static function invalidSampleRandDataProvider(): iterable
197+
{
198+
yield ['-1.0'];
199+
yield ['1'];
200+
yield ['2.0'];
201+
yield ['foo'];
202+
}
203+
138204
public function testSampleRandRangeWhenParentNotSampledAndSampleRateProvided(): void
139205
{
140206
$context = TransactionContext::fromHeaders(

0 commit comments

Comments
 (0)