From acd12bd0f57273c183e044424491db62c8ce6031 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Jun 2026 11:21:34 +0200 Subject: [PATCH 1/3] feat(scopes): extract transaction sampling logic into TransactionSampler --- src/State/Hub.php | 144 +--------- src/Tracing/TransactionSampler.php | 177 ++++++++++++ tests/Tracing/TransactionSamplerTest.php | 330 +++++++++++++++++++++++ 3 files changed, 509 insertions(+), 142 deletions(-) create mode 100644 src/Tracing/TransactionSampler.php create mode 100644 tests/Tracing/TransactionSamplerTest.php diff --git a/src/State/Hub.php b/src/State/Hub.php index e9132531e..2f4b947ed 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -16,10 +16,10 @@ use Sentry\MonitorConfig; use Sentry\NoOpClient; use Sentry\Severity; -use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionSampler; /** * This class is a basic implementation of the {@see HubInterface} interface. @@ -237,103 +237,8 @@ public function getIntegration(string $className): ?IntegrationInterface public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { $transaction = new Transaction($context, $this); - $options = $this->getClient()->getOptions(); - $logger = $options->getLoggerOrNullLogger(); - if (!$options->isTracingEnabled()) { - $transaction->setSampled(false); - - $logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); - - return $transaction; - } - - $samplingContext = SamplingContext::getDefault($context); - $samplingContext->setAdditionalContext($customSamplingContext); - - $sampleSource = 'context'; - $sampleRand = $context->getMetadata()->getSampleRand(); - - if ($transaction->getSampled() === null) { - $tracesSampler = $options->getTracesSampler(); - - if ($tracesSampler !== null) { - $sampleRate = $tracesSampler($samplingContext); - $sampleSource = 'config:traces_sampler'; - } else { - $parentSampleRate = $context->getMetadata()->getParentSamplingRate(); - if ($parentSampleRate !== null) { - $sampleRate = $parentSampleRate; - $sampleSource = 'parent:sample_rate'; - } else { - $sampleRate = $this->getSampleRate( - $samplingContext->getParentSampled(), - $options->getTracesSampleRate() ?? 0 - ); - $sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate'; - } - } - - if (!$this->isValidSampleRate($sampleRate)) { - $transaction->setSampled(false); - - $logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); - - return $transaction; - } - - $transaction->getMetadata()->setSamplingRate($sampleRate); - - // Always overwrite the sample_rate in the DSC - $dynamicSamplingContext = $context->getMetadata()->getDynamicSamplingContext(); - if ($dynamicSamplingContext !== null) { - $dynamicSamplingContext->set('sample_rate', (string) $sampleRate, true); - } - - if ($sampleRate === 0.0) { - $transaction->setSampled(false); - - $logger->info(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]); - - return $transaction; - } - - $transaction->setSampled($sampleRand < $sampleRate); - } - - if (!$transaction->getSampled()) { - $logger->info(\sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); - - return $transaction; - } - - $logger->info(\sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); - - $transaction->initSpanRecorder(); - - $profilesSampleSource = 'config:profiles_sample_rate'; - $profilesSampler = $options->getProfilesSampler(); - - if ($profilesSampler !== null) { - $profilesSampleRate = $profilesSampler($samplingContext); - $profilesSampleSource = 'config:profiles_sampler'; - } else { - $profilesSampleRate = $options->getProfilesSampleRate(); - } - - if ($profilesSampleRate === null) { - $logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId())); - } elseif (!$this->isValidSampleRate($profilesSampleRate)) { - $logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource)); - } elseif ($this->sample($profilesSampleRate)) { - $logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); - - $transaction->initProfiler()->start(); - } else { - $logger->info(\sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId())); - } - - return $transaction; + return (new TransactionSampler($this->getClient()->getOptions()))->startTransaction($transaction, $context, $customSamplingContext); } /** @@ -377,49 +282,4 @@ private function getStackTop(): Layer { return $this->stack[\count($this->stack) - 1]; } - - private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float - { - if ($hasParentBeenSampled === true) { - return 1.0; - } - - if ($hasParentBeenSampled === false) { - return 0.0; - } - - return $fallbackSampleRate; - } - - /** - * @param mixed $sampleRate - */ - private function sample($sampleRate): bool - { - if ($sampleRate === 0.0 || $sampleRate === null) { - return false; - } - - if ($sampleRate === 1.0) { - return true; - } - - return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < $sampleRate; - } - - /** - * @param mixed $sampleRate - */ - private function isValidSampleRate($sampleRate): bool - { - if (!\is_float($sampleRate) && !\is_int($sampleRate)) { - return false; - } - - if ($sampleRate < 0 || $sampleRate > 1) { - return false; - } - - return true; - } } diff --git a/src/Tracing/TransactionSampler.php b/src/Tracing/TransactionSampler.php new file mode 100644 index 000000000..c3bd46b9f --- /dev/null +++ b/src/Tracing/TransactionSampler.php @@ -0,0 +1,177 @@ +options = $options; + } + + /** + * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} + */ + public function startTransaction(Transaction $transaction, TransactionContext $context, array $customSamplingContext = []): Transaction + { + $logger = $this->options->getLoggerOrNullLogger(); + + if (!$this->options->isTracingEnabled()) { + $transaction->setSampled(false); + + $logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); + + return $transaction; + } + + $samplingContext = SamplingContext::getDefault($context); + $samplingContext->setAdditionalContext($customSamplingContext); + + $sampleSource = 'context'; + $sampleRand = $context->getMetadata()->getSampleRand() ?? 0.0; + + if ($transaction->getSampled() === null) { + $tracesSampler = $this->options->getTracesSampler(); + + if ($tracesSampler !== null) { + $sampleRate = $tracesSampler($samplingContext); + $sampleSource = 'config:traces_sampler'; + } else { + $parentSampleRate = $context->getMetadata()->getParentSamplingRate(); + if ($parentSampleRate !== null) { + $sampleRate = $parentSampleRate; + $sampleSource = 'parent:sample_rate'; + } else { + $sampleRate = $this->getSampleRate( + $samplingContext->getParentSampled(), + $this->options->getTracesSampleRate() ?? 0 + ); + $sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate'; + } + } + + if (!$this->isValidSampleRate($sampleRate)) { + $transaction->setSampled(false); + + $logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + + return $transaction; + } + + $transaction->getMetadata()->setSamplingRate($sampleRate); + + // Always overwrite the sample_rate in the DSC + $dynamicSamplingContext = $context->getMetadata()->getDynamicSamplingContext(); + if ($dynamicSamplingContext !== null) { + $dynamicSamplingContext->set('sample_rate', (string) $sampleRate, true); + } + + if ($sampleRate === 0.0) { + $transaction->setSampled(false); + + $logger->info(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is %s.', (string) $transaction->getTraceId(), $sampleSource, $sampleRate), ['context' => $context]); + + return $transaction; + } + + $transaction->setSampled($sampleRand < $sampleRate); + } + + if (!$transaction->getSampled()) { + $logger->info(\sprintf('Transaction [%s] was started but not sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + + return $transaction; + } + + $logger->info(\sprintf('Transaction [%s] was started and sampled, decided by %s.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); + + $transaction->initSpanRecorder(); + + $profilesSampleSource = 'config:profiles_sample_rate'; + $profilesSampler = $this->options->getProfilesSampler(); + + if ($profilesSampler !== null) { + $profilesSampleRate = $profilesSampler($samplingContext); + $profilesSampleSource = 'config:profiles_sampler'; + } else { + $profilesSampleRate = $this->options->getProfilesSampleRate(); + } + + if ($profilesSampleRate === null) { + $logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId())); + } elseif (!$this->isValidSampleRate($profilesSampleRate)) { + $logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource)); + } elseif ($this->sampleRate($profilesSampleRate)) { + $logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); + + $transaction->initProfiler()->start(); + } else { + $logger->info(\sprintf('Transaction [%s] is not profiling because it was not sampled.', (string) $transaction->getTraceId())); + } + + return $transaction; + } + + private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float + { + if ($hasParentBeenSampled === true) { + return 1.0; + } + + if ($hasParentBeenSampled === false) { + return 0.0; + } + + return $fallbackSampleRate; + } + + /** + * @param mixed $sampleRate + */ + private function sampleRate($sampleRate): bool + { + if (!\is_float($sampleRate) && !\is_int($sampleRate)) { + return false; + } + + if ($sampleRate === 0.0) { + return false; + } + + if ($sampleRate === 1.0) { + return true; + } + + return mt_rand(0, mt_getrandmax() - 1) / mt_getrandmax() < (float) $sampleRate; + } + + /** + * @param mixed $sampleRate + */ + private function isValidSampleRate($sampleRate): bool + { + if (!\is_float($sampleRate) && !\is_int($sampleRate)) { + return false; + } + + if ($sampleRate < 0 || $sampleRate > 1) { + return false; + } + + return true; + } +} diff --git a/tests/Tracing/TransactionSamplerTest.php b/tests/Tracing/TransactionSamplerTest.php new file mode 100644 index 000000000..1fe3aab72 --- /dev/null +++ b/tests/Tracing/TransactionSamplerTest.php @@ -0,0 +1,330 @@ +sampleTransaction($options, $transactionContext); + + $this->assertSame($expectedSampled, $transaction->getSampled()); + } + + public function testIgnoresBaggageSampleRateWithoutSentryTrace(): void + { + $transactionContext = TransactionContext::fromHeaders('', 'sentry-sample_rate=1'); + $transaction = $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 0.0, + ]), $transactionContext); + + $this->assertFalse($transaction->getSampled()); + } + + public static function sampleTransactionDataProvider(): iterable + { + yield 'Acceptable float value returned from traces_sampler' => [ + new Options([ + 'traces_sampler' => static function (): float { + return 1.0; + }, + ]), + new TransactionContext(), + true, + ]; + + yield 'Acceptable but too low float value returned from traces_sampler' => [ + new Options([ + 'traces_sampler' => static function (): float { + return 0.0; + }, + ]), + new TransactionContext(), + false, + ]; + + yield 'Acceptable integer value returned from traces_sampler' => [ + new Options([ + 'traces_sampler' => static function (): int { + return 1; + }, + ]), + new TransactionContext(), + true, + ]; + + yield 'Acceptable but too low integer value returned from traces_sampler' => [ + new Options([ + 'traces_sampler' => static function (): int { + return 0; + }, + ]), + new TransactionContext(), + false, + ]; + + yield 'Acceptable float value returned from traces_sample_rate' => [ + new Options([ + 'traces_sample_rate' => 1.0, + ]), + new TransactionContext(), + true, + ]; + + yield 'Acceptable but too low float value returned from traces_sample_rate' => [ + new Options([ + 'traces_sample_rate' => 0.0, + ]), + new TransactionContext(), + false, + ]; + + yield 'Acceptable integer value returned from traces_sample_rate' => [ + new Options([ + 'traces_sample_rate' => 1, + ]), + new TransactionContext(), + true, + ]; + + yield 'Acceptable but too low integer value returned from traces_sample_rate' => [ + new Options([ + 'traces_sample_rate' => 0, + ]), + new TransactionContext(), + false, + ]; + + yield 'Acceptable but too low value returned from traces_sample_rate which is preferred over sample_rate' => [ + new Options([ + 'sample_rate' => 1.0, + 'traces_sample_rate' => 0.0, + ]), + new TransactionContext(), + false, + ]; + + yield 'Acceptable value returned from traces_sample_rate which is preferred over sample_rate' => [ + new Options([ + 'sample_rate' => 0.0, + 'traces_sample_rate' => 1.0, + ]), + new TransactionContext(), + true, + ]; + + yield 'Acceptable value returned from SamplingContext::getParentSampled() which is preferred over traces_sample_rate (x1)' => [ + new Options([ + 'traces_sample_rate' => 0.5, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, true), + true, + ]; + + yield 'Acceptable value returned from SamplingContext::getParentSampled() which is preferred over traces_sample_rate (x2)' => [ + new Options([ + 'traces_sample_rate' => 1.0, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield 'Invalid incoming sample_rand is ignored' => [ + new Options([ + 'traces_sample_rate' => 1.0, + ]), + TransactionContext::fromHeaders( + '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8', + 'sentry-sample_rand=2.0' + ), + true, + ]; + + yield 'Out of range sample rate returned from traces_sampler (lower than minimum)' => [ + new Options([ + 'traces_sampler' => static function (): float { + return -1.0; + }, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield 'Out of range sample rate returned from traces_sampler (greater than maximum)' => [ + new Options([ + 'traces_sampler' => static function (): float { + return 1.1; + }, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + + yield 'Invalid type returned from traces_sampler' => [ + new Options([ + 'traces_sampler' => static function (): string { + return 'foo'; + }, + ]), + new TransactionContext(TransactionContext::DEFAULT_NAME, false), + false, + ]; + } + + public function testDoesNothingIfTracingIsNotEnabled(): void + { + $transaction = $this->sampleTransaction(new Options(), new TransactionContext()); + + $this->assertFalse($transaction->getSampled()); + } + + public function testPassesCustomSamplingContextToTracesSampler(): void + { + $customSamplingContext = ['a' => 'b']; + $samplerInvoked = false; + + $this->sampleTransaction(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext, &$samplerInvoked): float { + $this->assertSame($customSamplingContext, $samplingContext->getAdditionalContext()); + $samplerInvoked = true; + + return 1.0; + }, + ]), new TransactionContext(), $customSamplingContext); + + $this->assertTrue($samplerInvoked); + } + + public function testStartsProfilerWithProfilesSampler(): void + { + $transaction = $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 1.0; + }, + ]), new TransactionContext()); + + $this->assertTrue($transaction->getSampled()); + $this->assertNotNull($transaction->getProfiler()); + } + + public function testDoesNotStartProfilerWhenProfilesSamplerReturnsZero(): void + { + $transaction = $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 0.0; + }, + ]), new TransactionContext()); + + $this->assertTrue($transaction->getSampled()); + $this->assertNull($transaction->getProfiler()); + } + + public function testPrefersProfilesSamplerOverProfilesSampleRate(): void + { + $transaction = $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 0.0; + }, + ]), new TransactionContext()); + + $this->assertTrue($transaction->getSampled()); + $this->assertNull($transaction->getProfiler()); + } + + public function testPassesCustomSamplingContextToProfilesSampler(): void + { + $customSamplingContext = ['a' => 'b']; + $samplerInvoked = false; + + $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext, &$samplerInvoked): float { + $this->assertSame($customSamplingContext, $samplingContext->getAdditionalContext()); + $samplerInvoked = true; + + return 0.0; + }, + ]), new TransactionContext(), $customSamplingContext); + + $this->assertTrue($samplerInvoked); + } + + public function testDoesNotStartProfilerWhenProfilesSamplerReturnsInvalidValue(): void + { + $transaction = $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): string { + return 'foo'; + }, + ]), new TransactionContext()); + + $this->assertTrue($transaction->getSampled()); + $this->assertNull($transaction->getProfiler()); + } + + public function testDoesNotCallProfilesSamplerWhenTransactionIsNotSampled(): void + { + $profilesSamplerInvoked = false; + + $transaction = $this->sampleTransaction(new Options([ + 'traces_sample_rate' => 0.0, + 'profiles_sampler' => static function () use (&$profilesSamplerInvoked): float { + $profilesSamplerInvoked = true; + + return 1.0; + }, + ]), new TransactionContext()); + + $this->assertFalse($transaction->getSampled()); + $this->assertFalse($profilesSamplerInvoked); + $this->assertNull($transaction->getProfiler()); + } + + public function testUpdatesTheDscSampleRate(): void + { + $dsc = DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public'); + $transactionMetaData = new TransactionMetadata(null, $dsc); + $transactionContext = new TransactionContext(TransactionContext::DEFAULT_NAME, null, $transactionMetaData); + + $transaction = $this->sampleTransaction(new Options([ + 'traces_sampler' => static function (SamplingContext $samplingContext): float { + return 1.0; + }, + ]), $transactionContext); + + $this->assertSame('1', $transaction->getMetadata()->getDynamicSamplingContext()->get('sample_rate')); + } + + /** + * @param array $customSamplingContext + */ + private function sampleTransaction(Options $options, TransactionContext $transactionContext, array $customSamplingContext = []): Transaction + { + $client = $this->createMock(ClientInterface::class); + $client->method('getOptions')->willReturn($options); + + $transaction = new Transaction($transactionContext, new Hub($client)); + + return (new TransactionSampler($options))->startTransaction($transaction, $transactionContext, $customSamplingContext); + } +} From b3faabd8bcda6b08da171ac0256dd5843c81adbe Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Jun 2026 13:36:06 +0200 Subject: [PATCH 2/3] make TransactionSampler static --- src/State/Hub.php | 5 +- src/Tracing/TransactionSampler.php | 39 ++-- src/functions.php | 3 +- tests/FunctionsTest.php | 42 ++-- tests/State/HubTest.php | 186 ++++++------------ tests/Tracing/GuzzleTracingMiddlewareTest.php | 82 +++----- tests/Tracing/TransactionSamplerTest.php | 9 +- tests/Tracing/TransactionTest.php | 36 ++-- 8 files changed, 150 insertions(+), 252 deletions(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 2f4b947ed..5cebfbda4 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -19,7 +19,6 @@ use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -use Sentry\Tracing\TransactionSampler; /** * This class is a basic implementation of the {@see HubInterface} interface. @@ -236,9 +235,7 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - $transaction = new Transaction($context, $this); - - return (new TransactionSampler($this->getClient()->getOptions()))->startTransaction($transaction, $context, $customSamplingContext); + return \Sentry\startTransaction($context, $customSamplingContext); } /** diff --git a/src/Tracing/TransactionSampler.php b/src/Tracing/TransactionSampler.php index c3bd46b9f..6d946d680 100644 --- a/src/Tracing/TransactionSampler.php +++ b/src/Tracing/TransactionSampler.php @@ -7,30 +7,23 @@ use Sentry\Options; /** - * Applies tracing and profiling sampling decisions to transactions. - * * @internal */ final class TransactionSampler { - /** - * @var Options - */ - private $options; - - public function __construct(Options $options) + private function __construct() { - $this->options = $options; } /** * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public function startTransaction(Transaction $transaction, TransactionContext $context, array $customSamplingContext = []): Transaction + public static function startTransaction(Options $options, TransactionContext $context, array $customSamplingContext = []): Transaction { - $logger = $this->options->getLoggerOrNullLogger(); + $transaction = new Transaction($context); + $logger = $options->getLoggerOrNullLogger(); - if (!$this->options->isTracingEnabled()) { + if (!$options->isTracingEnabled()) { $transaction->setSampled(false); $logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); @@ -45,7 +38,7 @@ public function startTransaction(Transaction $transaction, TransactionContext $c $sampleRand = $context->getMetadata()->getSampleRand() ?? 0.0; if ($transaction->getSampled() === null) { - $tracesSampler = $this->options->getTracesSampler(); + $tracesSampler = $options->getTracesSampler(); if ($tracesSampler !== null) { $sampleRate = $tracesSampler($samplingContext); @@ -56,15 +49,15 @@ public function startTransaction(Transaction $transaction, TransactionContext $c $sampleRate = $parentSampleRate; $sampleSource = 'parent:sample_rate'; } else { - $sampleRate = $this->getSampleRate( + $sampleRate = self::getSampleRate( $samplingContext->getParentSampled(), - $this->options->getTracesSampleRate() ?? 0 + $options->getTracesSampleRate() ?? 0 ); $sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate'; } } - if (!$this->isValidSampleRate($sampleRate)) { + if (!self::isValidSampleRate($sampleRate)) { $transaction->setSampled(false); $logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); @@ -102,20 +95,20 @@ public function startTransaction(Transaction $transaction, TransactionContext $c $transaction->initSpanRecorder(); $profilesSampleSource = 'config:profiles_sample_rate'; - $profilesSampler = $this->options->getProfilesSampler(); + $profilesSampler = $options->getProfilesSampler(); if ($profilesSampler !== null) { $profilesSampleRate = $profilesSampler($samplingContext); $profilesSampleSource = 'config:profiles_sampler'; } else { - $profilesSampleRate = $this->options->getProfilesSampleRate(); + $profilesSampleRate = $options->getProfilesSampleRate(); } if ($profilesSampleRate === null) { $logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId())); - } elseif (!$this->isValidSampleRate($profilesSampleRate)) { + } elseif (!self::isValidSampleRate($profilesSampleRate)) { $logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource)); - } elseif ($this->sampleRate($profilesSampleRate)) { + } elseif (self::sampleRate($profilesSampleRate)) { $logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); $transaction->initProfiler()->start(); @@ -126,7 +119,7 @@ public function startTransaction(Transaction $transaction, TransactionContext $c return $transaction; } - private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float + private static function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float { if ($hasParentBeenSampled === true) { return 1.0; @@ -142,7 +135,7 @@ private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampl /** * @param mixed $sampleRate */ - private function sampleRate($sampleRate): bool + private static function sampleRate($sampleRate): bool { if (!\is_float($sampleRate) && !\is_int($sampleRate)) { return false; @@ -162,7 +155,7 @@ private function sampleRate($sampleRate): bool /** * @param mixed $sampleRate */ - private function isValidSampleRate($sampleRate): bool + private static function isValidSampleRate($sampleRate): bool { if (!\is_float($sampleRate) && !\is_int($sampleRate)) { return false; diff --git a/src/functions.php b/src/functions.php index 1549bf9fe..b48dc728b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -15,6 +15,7 @@ use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionSampler; use Sentry\Transport\TransportInterface; /** @@ -268,7 +269,7 @@ function withContext(callable $callback, ?int $timeout = null) */ function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); + return TransactionSampler::startTransaction(SentrySdk::getClient()->getOptions(), $context, $customSamplingContext); } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index ba349bde9..57a2e8f79 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; use Sentry\CheckInStatus; +use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; @@ -23,6 +24,7 @@ use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; +use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; @@ -452,18 +454,28 @@ public function testWithContextAlwaysEndsContextWithOptionalTimeout(): void public function testStartTransaction(): void { $transactionContext = new TransactionContext('foo'); - $transaction = new Transaction($transactionContext); $customSamplingContext = ['foo' => 'bar']; + $samplerInvoked = false; - $hub = $this->createMock(HubInterface::class); - $hub->expects($this->once()) - ->method('startTransaction') - ->with($transactionContext, $customSamplingContext) - ->willReturn($transaction); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext, &$samplerInvoked): float { + $this->assertSame($customSamplingContext, $samplingContext->getAdditionalContext()); + $samplerInvoked = true; - SentrySdk::setCurrentHub($hub); + return 1.0; + }, + ])); + + SentrySdk::init($client); - $this->assertSame($transaction, startTransaction($transactionContext, $customSamplingContext)); + $transaction = startTransaction($transactionContext, $customSamplingContext); + + $this->assertSame('foo', $transaction->getName()); + $this->assertTrue($transaction->getSampled()); + $this->assertTrue($samplerInvoked); } public function testTraceReturnsClosureResult(): void @@ -620,17 +632,15 @@ public function testBaggageWithTracingDisabled(): void public function testBaggageWithTracingEnabled(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeastOnce()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'release' => '1.0.0', - 'environment' => 'development', - ])); + $client = new Client(new Options([ + 'traces_sample_rate' => 1.0, + 'release' => '1.0.0', + 'environment' => 'development', + ]), StubTransport::getInstance()); $hub = new Hub($client); + SentrySdk::getGlobalScope()->setClient($client); SentrySdk::setCurrentHub($hub); $transactionContext = new TransactionContext(); diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 972ce617e..74eac77f3 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -9,6 +9,7 @@ use Sentry\Breadcrumb; use Sentry\CheckIn; use Sentry\CheckInStatus; +use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; @@ -18,9 +19,11 @@ use Sentry\MonitorSchedule; use Sentry\NoOpClient; use Sentry\Options; +use Sentry\SentrySdk; use Sentry\Severity; use Sentry\State\Hub; use Sentry\State\Scope; +use Sentry\Tests\StubTransport; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SamplingContext; @@ -626,12 +629,7 @@ public function testGetIntegration(): void */ public function testStartTransactionWithTracesSampler(Options $options, TransactionContext $transactionContext, bool $expectedSampled): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn($options); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client($options, StubTransport::getInstance())); $transaction = $hub->startTransaction($transactionContext); $this->assertSame($expectedSampled, $transaction->getSampled()); @@ -639,14 +637,9 @@ public function testStartTransactionWithTracesSampler(Options $options, Transact public function testStartTransactionIgnoresBaggageSampleRateWithoutSentryTrace(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 0.0, - ])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 0.0, + ]), StubTransport::getInstance())); $transactionContext = TransactionContext::fromHeaders('', 'sentry-sample_rate=1'); $transaction = $hub->startTransaction($transactionContext); @@ -805,12 +798,7 @@ public static function startTransactionDataProvider(): iterable public function testStartTransactionDoesNothingIfTracingIsNotEnabled(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options()); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options(), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertFalse($transaction->getSampled()); @@ -820,34 +808,25 @@ public function testStartTransactionWithCustomSamplingContext(): void { $customSamplingContext = ['a' => 'b']; - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { - $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { + $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); - return 1.0; - }, - ])); + return 1.0; + }, + ]), StubTransport::getInstance())); - $hub = new Hub($client); $hub->startTransaction(new TransactionContext(), $customSamplingContext); } public function testStartTransactionStartsProfilerWithProfilesSampler(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->exactly(2)) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => static function (): float { - return 1.0; - }, - ])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 1.0; + }, + ]), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -856,17 +835,12 @@ public function testStartTransactionStartsProfilerWithProfilesSampler(): void public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerReturnsZero(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => static function (): float { - return 0.0; - }, - ])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 0.0; + }, + ]), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -875,18 +849,13 @@ public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerRetur public function testStartTransactionPrefersProfilesSamplerOverProfilesSampleRate(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sample_rate' => 1.0, - 'profiles_sampler' => static function (): float { - return 0.0; - }, - ])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 0.0; + }, + ]), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -897,35 +866,26 @@ public function testStartTransactionWithProfilesSamplerReceivesCustomSamplingCon { $customSamplingContext = ['a' => 'b']; - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { - $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { + $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); - return 0.0; - }, - ])); + return 0.0; + }, + ]), StubTransport::getInstance())); - $hub = new Hub($client); $hub->startTransaction(new TransactionContext(), $customSamplingContext); } public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerReturnsInvalidValue(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => static function (): string { - return 'foo'; - }, - ])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): string { + return 'foo'; + }, + ]), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -936,19 +896,15 @@ public function testStartTransactionDoesNotCallProfilesSamplerWhenTransactionIsN { $profilesSamplerInvoked = false; - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 0.0, - 'profiles_sampler' => static function () use (&$profilesSamplerInvoked): float { - $profilesSamplerInvoked = true; + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sample_rate' => 0.0, + 'profiles_sampler' => static function () use (&$profilesSamplerInvoked): float { + $profilesSamplerInvoked = true; - return 1.0; - }, - ])); + return 1.0; + }, + ]), StubTransport::getInstance())); - $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertFalse($transaction->getSampled()); @@ -958,16 +914,11 @@ public function testStartTransactionDoesNotCallProfilesSamplerWhenTransactionIsN public function testStartTransactionUpdatesTheDscSampleRate(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sampler' => static function (SamplingContext $samplingContext): float { - return 1.0; - }, - ])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options([ + 'traces_sampler' => static function (SamplingContext $samplingContext): float { + return 1.0; + }, + ]), StubTransport::getInstance())); $dsc = DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public'); $transactionMetaData = new TransactionMetadata(null, $dsc); @@ -979,12 +930,7 @@ public function testStartTransactionUpdatesTheDscSampleRate(): void public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsNotSampled(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options(['traces_sample_rate' => 1])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options(['traces_sample_rate' => 1]), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, false)); $hub->configureScope(static function (Scope $scope) use ($transaction): void { @@ -996,12 +942,7 @@ public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsNot public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsSampled(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options(['traces_sample_rate' => 1])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options(['traces_sample_rate' => 1]), StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, true)); $hub->configureScope(static function (Scope $scope) use ($transaction): void { @@ -1013,12 +954,7 @@ public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsSam public function testGetTransactionReturnsNullIfNoTransactionIsSetOnTheScope(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options(['traces_sample_rate' => 1])); - - $hub = new Hub($client); + $hub = SentrySdk::init(new Client(new Options(['traces_sample_rate' => 1]), StubTransport::getInstance())); $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, true)); $this->assertNull($hub->getTransaction()); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index becfa84a8..fffeb3482 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -11,13 +11,13 @@ use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Uri; use PHPUnit\Framework\TestCase; -use Sentry\ClientInterface; +use Sentry\Client; use Sentry\Event; use Sentry\EventType; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\State\Hub; use Sentry\State\Scope; +use Sentry\Tests\StubTransport; use Sentry\Tracing\GuzzleTracingMiddleware; use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TransactionContext; @@ -26,15 +26,11 @@ final class GuzzleTracingMiddlewareTest extends TestCase { public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeast(2)) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 0, - ])); + $client = new Client(new Options([ + 'traces_sample_rate' => 0, + ]), StubTransport::getInstance()); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + $hub = SentrySdk::init($client); $transaction = $hub->startTransaction(TransactionContext::make()); @@ -71,15 +67,11 @@ public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeast(2)) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1, - ])); + $client = new Client(new Options([ + 'traces_sample_rate' => 1, + ]), StubTransport::getInstance()); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + $hub = SentrySdk::init($client); $transaction = $hub->startTransaction(TransactionContext::make()); @@ -120,13 +112,7 @@ public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void */ public function testTraceHeaders(Request $request, Options $options, bool $headersShouldBePresent): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeastOnce()) - ->method('getOptions') - ->willReturn($options); - - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + $hub = SentrySdk::init(new Client($options, StubTransport::getInstance())); $expectedPromiseResult = new Response(); @@ -152,13 +138,7 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade */ public function testTraceHeadersWithTransaction(Request $request, Options $options, bool $headersShouldBePresent): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeast(2)) - ->method('getOptions') - ->willReturn($options); - - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + $hub = SentrySdk::init(new Client($options, StubTransport::getInstance())); $transaction = $hub->startTransaction(new TransactionContext()); @@ -194,15 +174,9 @@ public function testTraceHeadersAreNotAddedWhenExternalPropagationContextIsActiv ]; }); - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeastOnce()) - ->method('getOptions') - ->willReturn(new Options([ - 'trace_propagation_targets' => null, - ])); - - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + $hub = SentrySdk::init(new Client(new Options([ + 'trace_propagation_targets' => null, + ]), StubTransport::getInstance())); $expectedPromiseResult = new Response(); $middleware = GuzzleTracingMiddleware::trace($hub); @@ -319,18 +293,20 @@ public static function traceHeadersDataProvider(): iterable */ public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData, array $expectedSpanData): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->atLeast(4)) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sample_rate' => 1, - 'trace_propagation_targets' => [ - 'www.example.com', - ], - ])); - - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + $client = $this->getMockBuilder(Client::class) + ->setConstructorArgs([ + new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => [ + 'www.example.com', + ], + ]), + StubTransport::getInstance(), + ]) + ->onlyMethods(['captureEvent']) + ->getMock(); + + $hub = SentrySdk::init($client); $client->expects($this->once()) ->method('captureEvent') diff --git a/tests/Tracing/TransactionSamplerTest.php b/tests/Tracing/TransactionSamplerTest.php index 1fe3aab72..9118e9c12 100644 --- a/tests/Tracing/TransactionSamplerTest.php +++ b/tests/Tracing/TransactionSamplerTest.php @@ -5,9 +5,7 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; -use Sentry\ClientInterface; use Sentry\Options; -use Sentry\State\Hub; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Transaction; @@ -320,11 +318,6 @@ public function testUpdatesTheDscSampleRate(): void */ private function sampleTransaction(Options $options, TransactionContext $transactionContext, array $customSamplingContext = []): Transaction { - $client = $this->createMock(ClientInterface::class); - $client->method('getOptions')->willReturn($options); - - $transaction = new Transaction($transactionContext, new Hub($client)); - - return (new TransactionSampler($options))->startTransaction($transaction, $transactionContext, $customSamplingContext); + return TransactionSampler::startTransaction($options, $transactionContext, $customSamplingContext); } } diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index 76a2c3aab..f27b12825 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -5,13 +5,15 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventId; use Sentry\EventType; use Sentry\Options; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\HubInterface; +use Sentry\Tests\StubTransport; use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; @@ -102,17 +104,12 @@ public function testFluentApi(): void */ public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions(TransactionContext $context, bool $expectedSampled): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn( - new Options([ - 'traces_sampler' => null, - 'traces_sample_rate' => 0, - ]) - ); + $client = new Client(new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => 0, + ]), StubTransport::getInstance()); - $transaction = (new Hub($client))->startTransaction($context); + $transaction = SentrySdk::init($client)->startTransaction($context); $this->assertSame($expectedSampled, $transaction->getSampled()); } @@ -145,17 +142,12 @@ public static function parentTransactionContextDataProvider(): \Generator */ public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void { - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn( - new Options([ - 'traces_sampler' => null, - 'traces_sample_rate' => null, - ]) - ); - - $transaction = (new Hub($client))->startTransaction($context); + $client = new Client(new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => null, + ]), StubTransport::getInstance()); + + $transaction = SentrySdk::init($client)->startTransaction($context); $this->assertSame($expectedSampled, $transaction->getSampled()); } From 472a3ab90185be2e340e1f4b00195af6d61be80f Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 11 Jun 2026 15:54:32 +0200 Subject: [PATCH 3/3] Revert "make TransactionSampler static" This reverts commit f28ee44b7fb05f7b33e28b2cc4d3b65d7d4d17be. --- src/State/Hub.php | 5 +- src/Tracing/TransactionSampler.php | 39 ++-- src/functions.php | 3 +- tests/FunctionsTest.php | 42 ++-- tests/State/HubTest.php | 186 ++++++++++++------ tests/Tracing/GuzzleTracingMiddlewareTest.php | 82 +++++--- tests/Tracing/TransactionSamplerTest.php | 9 +- tests/Tracing/TransactionTest.php | 36 ++-- 8 files changed, 252 insertions(+), 150 deletions(-) diff --git a/src/State/Hub.php b/src/State/Hub.php index 5cebfbda4..2f4b947ed 100644 --- a/src/State/Hub.php +++ b/src/State/Hub.php @@ -19,6 +19,7 @@ use Sentry\Tracing\Span; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionSampler; /** * This class is a basic implementation of the {@see HubInterface} interface. @@ -235,7 +236,9 @@ public function getIntegration(string $className): ?IntegrationInterface */ public function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - return \Sentry\startTransaction($context, $customSamplingContext); + $transaction = new Transaction($context, $this); + + return (new TransactionSampler($this->getClient()->getOptions()))->startTransaction($transaction, $context, $customSamplingContext); } /** diff --git a/src/Tracing/TransactionSampler.php b/src/Tracing/TransactionSampler.php index 6d946d680..c3bd46b9f 100644 --- a/src/Tracing/TransactionSampler.php +++ b/src/Tracing/TransactionSampler.php @@ -7,23 +7,30 @@ use Sentry\Options; /** + * Applies tracing and profiling sampling decisions to transactions. + * * @internal */ final class TransactionSampler { - private function __construct() + /** + * @var Options + */ + private $options; + + public function __construct(Options $options) { + $this->options = $options; } /** * @param array $customSamplingContext Additional context that will be passed to the {@see SamplingContext} */ - public static function startTransaction(Options $options, TransactionContext $context, array $customSamplingContext = []): Transaction + public function startTransaction(Transaction $transaction, TransactionContext $context, array $customSamplingContext = []): Transaction { - $transaction = new Transaction($context); - $logger = $options->getLoggerOrNullLogger(); + $logger = $this->options->getLoggerOrNullLogger(); - if (!$options->isTracingEnabled()) { + if (!$this->options->isTracingEnabled()) { $transaction->setSampled(false); $logger->warning(\sprintf('Transaction [%s] was started but tracing is not enabled.', (string) $transaction->getTraceId()), ['context' => $context]); @@ -38,7 +45,7 @@ public static function startTransaction(Options $options, TransactionContext $co $sampleRand = $context->getMetadata()->getSampleRand() ?? 0.0; if ($transaction->getSampled() === null) { - $tracesSampler = $options->getTracesSampler(); + $tracesSampler = $this->options->getTracesSampler(); if ($tracesSampler !== null) { $sampleRate = $tracesSampler($samplingContext); @@ -49,15 +56,15 @@ public static function startTransaction(Options $options, TransactionContext $co $sampleRate = $parentSampleRate; $sampleSource = 'parent:sample_rate'; } else { - $sampleRate = self::getSampleRate( + $sampleRate = $this->getSampleRate( $samplingContext->getParentSampled(), - $options->getTracesSampleRate() ?? 0 + $this->options->getTracesSampleRate() ?? 0 ); $sampleSource = $samplingContext->getParentSampled() !== null ? 'parent:sampling_decision' : 'config:traces_sample_rate'; } } - if (!self::isValidSampleRate($sampleRate)) { + if (!$this->isValidSampleRate($sampleRate)) { $transaction->setSampled(false); $logger->warning(\sprintf('Transaction [%s] was started but not sampled because sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $sampleSource), ['context' => $context]); @@ -95,20 +102,20 @@ public static function startTransaction(Options $options, TransactionContext $co $transaction->initSpanRecorder(); $profilesSampleSource = 'config:profiles_sample_rate'; - $profilesSampler = $options->getProfilesSampler(); + $profilesSampler = $this->options->getProfilesSampler(); if ($profilesSampler !== null) { $profilesSampleRate = $profilesSampler($samplingContext); $profilesSampleSource = 'config:profiles_sampler'; } else { - $profilesSampleRate = $options->getProfilesSampleRate(); + $profilesSampleRate = $this->options->getProfilesSampleRate(); } if ($profilesSampleRate === null) { $logger->info(\sprintf('Transaction [%s] is not profiling because neither `profiles_sample_rate` nor `profiles_sampler` option is set.', (string) $transaction->getTraceId())); - } elseif (!self::isValidSampleRate($profilesSampleRate)) { + } elseif (!$this->isValidSampleRate($profilesSampleRate)) { $logger->warning(\sprintf('Transaction [%s] is not profiling because profile sample rate (decided by %s) is invalid.', (string) $transaction->getTraceId(), $profilesSampleSource)); - } elseif (self::sampleRate($profilesSampleRate)) { + } elseif ($this->sampleRate($profilesSampleRate)) { $logger->info(\sprintf('Transaction [%s] started profiling because it was sampled.', (string) $transaction->getTraceId())); $transaction->initProfiler()->start(); @@ -119,7 +126,7 @@ public static function startTransaction(Options $options, TransactionContext $co return $transaction; } - private static function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float + private function getSampleRate(?bool $hasParentBeenSampled, float $fallbackSampleRate): float { if ($hasParentBeenSampled === true) { return 1.0; @@ -135,7 +142,7 @@ private static function getSampleRate(?bool $hasParentBeenSampled, float $fallba /** * @param mixed $sampleRate */ - private static function sampleRate($sampleRate): bool + private function sampleRate($sampleRate): bool { if (!\is_float($sampleRate) && !\is_int($sampleRate)) { return false; @@ -155,7 +162,7 @@ private static function sampleRate($sampleRate): bool /** * @param mixed $sampleRate */ - private static function isValidSampleRate($sampleRate): bool + private function isValidSampleRate($sampleRate): bool { if (!\is_float($sampleRate) && !\is_int($sampleRate)) { return false; diff --git a/src/functions.php b/src/functions.php index b48dc728b..1549bf9fe 100644 --- a/src/functions.php +++ b/src/functions.php @@ -15,7 +15,6 @@ use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; use Sentry\Tracing\TransactionContext; -use Sentry\Tracing\TransactionSampler; use Sentry\Transport\TransportInterface; /** @@ -269,7 +268,7 @@ function withContext(callable $callback, ?int $timeout = null) */ function startTransaction(TransactionContext $context, array $customSamplingContext = []): Transaction { - return TransactionSampler::startTransaction(SentrySdk::getClient()->getOptions(), $context, $customSamplingContext); + return SentrySdk::getCurrentHub()->startTransaction($context, $customSamplingContext); } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 57a2e8f79..ba349bde9 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; use Sentry\CheckInStatus; -use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; @@ -24,7 +23,6 @@ use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; -use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanId; @@ -454,28 +452,18 @@ public function testWithContextAlwaysEndsContextWithOptionalTimeout(): void public function testStartTransaction(): void { $transactionContext = new TransactionContext('foo'); + $transaction = new Transaction($transactionContext); $customSamplingContext = ['foo' => 'bar']; - $samplerInvoked = false; - $client = $this->createMock(ClientInterface::class); - $client->expects($this->once()) - ->method('getOptions') - ->willReturn(new Options([ - 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext, &$samplerInvoked): float { - $this->assertSame($customSamplingContext, $samplingContext->getAdditionalContext()); - $samplerInvoked = true; - - return 1.0; - }, - ])); - - SentrySdk::init($client); + $hub = $this->createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('startTransaction') + ->with($transactionContext, $customSamplingContext) + ->willReturn($transaction); - $transaction = startTransaction($transactionContext, $customSamplingContext); + SentrySdk::setCurrentHub($hub); - $this->assertSame('foo', $transaction->getName()); - $this->assertTrue($transaction->getSampled()); - $this->assertTrue($samplerInvoked); + $this->assertSame($transaction, startTransaction($transactionContext, $customSamplingContext)); } public function testTraceReturnsClosureResult(): void @@ -632,15 +620,17 @@ public function testBaggageWithTracingDisabled(): void public function testBaggageWithTracingEnabled(): void { - $client = new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'release' => '1.0.0', - 'environment' => 'development', - ]), StubTransport::getInstance()); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'release' => '1.0.0', + 'environment' => 'development', + ])); $hub = new Hub($client); - SentrySdk::getGlobalScope()->setClient($client); SentrySdk::setCurrentHub($hub); $transactionContext = new TransactionContext(); diff --git a/tests/State/HubTest.php b/tests/State/HubTest.php index 74eac77f3..972ce617e 100644 --- a/tests/State/HubTest.php +++ b/tests/State/HubTest.php @@ -9,7 +9,6 @@ use Sentry\Breadcrumb; use Sentry\CheckIn; use Sentry\CheckInStatus; -use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; @@ -19,11 +18,9 @@ use Sentry\MonitorSchedule; use Sentry\NoOpClient; use Sentry\Options; -use Sentry\SentrySdk; use Sentry\Severity; use Sentry\State\Hub; use Sentry\State\Scope; -use Sentry\Tests\StubTransport; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SamplingContext; @@ -629,7 +626,12 @@ public function testGetIntegration(): void */ public function testStartTransactionWithTracesSampler(Options $options, TransactionContext $transactionContext, bool $expectedSampled): void { - $hub = SentrySdk::init(new Client($options, StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn($options); + + $hub = new Hub($client); $transaction = $hub->startTransaction($transactionContext); $this->assertSame($expectedSampled, $transaction->getSampled()); @@ -637,9 +639,14 @@ public function testStartTransactionWithTracesSampler(Options $options, Transact public function testStartTransactionIgnoresBaggageSampleRateWithoutSentryTrace(): void { - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 0.0, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 0.0, + ])); + + $hub = new Hub($client); $transactionContext = TransactionContext::fromHeaders('', 'sentry-sample_rate=1'); $transaction = $hub->startTransaction($transactionContext); @@ -798,7 +805,12 @@ public static function startTransactionDataProvider(): iterable public function testStartTransactionDoesNothingIfTracingIsNotEnabled(): void { - $hub = SentrySdk::init(new Client(new Options(), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertFalse($transaction->getSampled()); @@ -808,25 +820,34 @@ public function testStartTransactionWithCustomSamplingContext(): void { $customSamplingContext = ['a' => 'b']; - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { - $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { + $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); - return 1.0; - }, - ]), StubTransport::getInstance())); + return 1.0; + }, + ])); + $hub = new Hub($client); $hub->startTransaction(new TransactionContext(), $customSamplingContext); } public function testStartTransactionStartsProfilerWithProfilesSampler(): void { - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => static function (): float { - return 1.0; - }, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->exactly(2)) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 1.0; + }, + ])); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -835,12 +856,17 @@ public function testStartTransactionStartsProfilerWithProfilesSampler(): void public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerReturnsZero(): void { - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => static function (): float { - return 0.0; - }, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 0.0; + }, + ])); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -849,13 +875,18 @@ public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerRetur public function testStartTransactionPrefersProfilesSamplerOverProfilesSampleRate(): void { - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sample_rate' => 1.0, - 'profiles_sampler' => static function (): float { - return 0.0; - }, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sample_rate' => 1.0, + 'profiles_sampler' => static function (): float { + return 0.0; + }, + ])); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -866,26 +897,35 @@ public function testStartTransactionWithProfilesSamplerReceivesCustomSamplingCon { $customSamplingContext = ['a' => 'b']; - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { - $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => function (SamplingContext $samplingContext) use ($customSamplingContext): float { + $this->assertSame($samplingContext->getAdditionalContext(), $customSamplingContext); - return 0.0; - }, - ]), StubTransport::getInstance())); + return 0.0; + }, + ])); + $hub = new Hub($client); $hub->startTransaction(new TransactionContext(), $customSamplingContext); } public function testStartTransactionDoesNotStartProfilerWhenProfilesSamplerReturnsInvalidValue(): void { - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 1.0, - 'profiles_sampler' => static function (): string { - return 'foo'; - }, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1.0, + 'profiles_sampler' => static function (): string { + return 'foo'; + }, + ])); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertTrue($transaction->getSampled()); @@ -896,15 +936,19 @@ public function testStartTransactionDoesNotCallProfilesSamplerWhenTransactionIsN { $profilesSamplerInvoked = false; - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sample_rate' => 0.0, - 'profiles_sampler' => static function () use (&$profilesSamplerInvoked): float { - $profilesSamplerInvoked = true; + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 0.0, + 'profiles_sampler' => static function () use (&$profilesSamplerInvoked): float { + $profilesSamplerInvoked = true; - return 1.0; - }, - ]), StubTransport::getInstance())); + return 1.0; + }, + ])); + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext()); $this->assertFalse($transaction->getSampled()); @@ -914,11 +958,16 @@ public function testStartTransactionDoesNotCallProfilesSamplerWhenTransactionIsN public function testStartTransactionUpdatesTheDscSampleRate(): void { - $hub = SentrySdk::init(new Client(new Options([ - 'traces_sampler' => static function (SamplingContext $samplingContext): float { - return 1.0; - }, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sampler' => static function (SamplingContext $samplingContext): float { + return 1.0; + }, + ])); + + $hub = new Hub($client); $dsc = DynamicSamplingContext::fromHeader('sentry-trace_id=d49d9bf66f13450b81f65bc51cf49c03,sentry-public_key=public'); $transactionMetaData = new TransactionMetadata(null, $dsc); @@ -930,7 +979,12 @@ public function testStartTransactionUpdatesTheDscSampleRate(): void public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsNotSampled(): void { - $hub = SentrySdk::init(new Client(new Options(['traces_sample_rate' => 1]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, false)); $hub->configureScope(static function (Scope $scope) use ($transaction): void { @@ -942,7 +996,12 @@ public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsNot public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsSampled(): void { - $hub = SentrySdk::init(new Client(new Options(['traces_sample_rate' => 1]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); $transaction = $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, true)); $hub->configureScope(static function (Scope $scope) use ($transaction): void { @@ -954,7 +1013,12 @@ public function testGetTransactionReturnsInstanceSetOnTheScopeIfTransactionIsSam public function testGetTransactionReturnsNullIfNoTransactionIsSetOnTheScope(): void { - $hub = SentrySdk::init(new Client(new Options(['traces_sample_rate' => 1]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options(['traces_sample_rate' => 1])); + + $hub = new Hub($client); $hub->startTransaction(new TransactionContext(TransactionContext::DEFAULT_NAME, true)); $this->assertNull($hub->getTransaction()); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index fffeb3482..becfa84a8 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -11,13 +11,13 @@ use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Uri; use PHPUnit\Framework\TestCase; -use Sentry\Client; +use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventType; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\State\Hub; use Sentry\State\Scope; -use Sentry\Tests\StubTransport; use Sentry\Tracing\GuzzleTracingMiddleware; use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TransactionContext; @@ -26,11 +26,15 @@ final class GuzzleTracingMiddlewareTest extends TestCase { public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void { - $client = new Client(new Options([ - 'traces_sample_rate' => 0, - ]), StubTransport::getInstance()); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeast(2)) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 0, + ])); - $hub = SentrySdk::init($client); + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); $transaction = $hub->startTransaction(TransactionContext::make()); @@ -67,11 +71,15 @@ public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void { - $client = new Client(new Options([ - 'traces_sample_rate' => 1, - ]), StubTransport::getInstance()); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeast(2)) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1, + ])); - $hub = SentrySdk::init($client); + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); $transaction = $hub->startTransaction(TransactionContext::make()); @@ -112,7 +120,13 @@ public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void */ public function testTraceHeaders(Request $request, Options $options, bool $headersShouldBePresent): void { - $hub = SentrySdk::init(new Client($options, StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn($options); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); $expectedPromiseResult = new Response(); @@ -138,7 +152,13 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade */ public function testTraceHeadersWithTransaction(Request $request, Options $options, bool $headersShouldBePresent): void { - $hub = SentrySdk::init(new Client($options, StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeast(2)) + ->method('getOptions') + ->willReturn($options); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); $transaction = $hub->startTransaction(new TransactionContext()); @@ -174,9 +194,15 @@ public function testTraceHeadersAreNotAddedWhenExternalPropagationContextIsActiv ]; }); - $hub = SentrySdk::init(new Client(new Options([ - 'trace_propagation_targets' => null, - ]), StubTransport::getInstance())); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'trace_propagation_targets' => null, + ])); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); $expectedPromiseResult = new Response(); $middleware = GuzzleTracingMiddleware::trace($hub); @@ -293,20 +319,18 @@ public static function traceHeadersDataProvider(): iterable */ public function testTrace(Request $request, $expectedPromiseResult, array $expectedBreadcrumbData, array $expectedSpanData): void { - $client = $this->getMockBuilder(Client::class) - ->setConstructorArgs([ - new Options([ - 'traces_sample_rate' => 1, - 'trace_propagation_targets' => [ - 'www.example.com', - ], - ]), - StubTransport::getInstance(), - ]) - ->onlyMethods(['captureEvent']) - ->getMock(); - - $hub = SentrySdk::init($client); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeast(4)) + ->method('getOptions') + ->willReturn(new Options([ + 'traces_sample_rate' => 1, + 'trace_propagation_targets' => [ + 'www.example.com', + ], + ])); + + $hub = new Hub($client); + SentrySdk::setCurrentHub($hub); $client->expects($this->once()) ->method('captureEvent') diff --git a/tests/Tracing/TransactionSamplerTest.php b/tests/Tracing/TransactionSamplerTest.php index 9118e9c12..1fe3aab72 100644 --- a/tests/Tracing/TransactionSamplerTest.php +++ b/tests/Tracing/TransactionSamplerTest.php @@ -5,7 +5,9 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\ClientInterface; use Sentry\Options; +use Sentry\State\Hub; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\SamplingContext; use Sentry\Tracing\Transaction; @@ -318,6 +320,11 @@ public function testUpdatesTheDscSampleRate(): void */ private function sampleTransaction(Options $options, TransactionContext $transactionContext, array $customSamplingContext = []): Transaction { - return TransactionSampler::startTransaction($options, $transactionContext, $customSamplingContext); + $client = $this->createMock(ClientInterface::class); + $client->method('getOptions')->willReturn($options); + + $transaction = new Transaction($transactionContext, new Hub($client)); + + return (new TransactionSampler($options))->startTransaction($transaction, $transactionContext, $customSamplingContext); } } diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index f27b12825..76a2c3aab 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -5,15 +5,13 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; -use Sentry\Client; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventId; use Sentry\EventType; use Sentry\Options; -use Sentry\SentrySdk; +use Sentry\State\Hub; use Sentry\State\HubInterface; -use Sentry\Tests\StubTransport; use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; @@ -104,12 +102,17 @@ public function testFluentApi(): void */ public function testTransactionIsSampledCorrectlyWhenTracingIsSetToZeroInOptions(TransactionContext $context, bool $expectedSampled): void { - $client = new Client(new Options([ - 'traces_sampler' => null, - 'traces_sample_rate' => 0, - ]), StubTransport::getInstance()); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn( + new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => 0, + ]) + ); - $transaction = SentrySdk::init($client)->startTransaction($context); + $transaction = (new Hub($client))->startTransaction($context); $this->assertSame($expectedSampled, $transaction->getSampled()); } @@ -142,12 +145,17 @@ public static function parentTransactionContextDataProvider(): \Generator */ public function testTransactionIsNotSampledWhenTracingIsDisabledInOptions(TransactionContext $context, bool $expectedSampled): void { - $client = new Client(new Options([ - 'traces_sampler' => null, - 'traces_sample_rate' => null, - ]), StubTransport::getInstance()); - - $transaction = SentrySdk::init($client)->startTransaction($context); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn( + new Options([ + 'traces_sampler' => null, + 'traces_sample_rate' => null, + ]) + ); + + $transaction = (new Hub($client))->startTransaction($context); $this->assertSame($expectedSampled, $transaction->getSampled()); }