diff --git a/src/Integration/EnvironmentIntegration.php b/src/Integration/EnvironmentIntegration.php index c404216ea..adf4f6a53 100644 --- a/src/Integration/EnvironmentIntegration.php +++ b/src/Integration/EnvironmentIntegration.php @@ -24,7 +24,7 @@ final class EnvironmentIntegration implements IntegrationInterface public function setupOnce(): void { Scope::addGlobalEventProcessor(static function (Event $event): Event { - $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); + $integration = SentrySdk::getClient()->getIntegration(self::class); if ($integration !== null) { $event->setRuntimeContext($integration->updateRuntimeContext($event->getRuntimeContext())); diff --git a/src/Integration/FrameContextifierIntegration.php b/src/Integration/FrameContextifierIntegration.php index 8e55a9727..b7ea13b40 100644 --- a/src/Integration/FrameContextifierIntegration.php +++ b/src/Integration/FrameContextifierIntegration.php @@ -41,7 +41,7 @@ public function __construct(?LoggerInterface $logger = null) public function setupOnce(): void { Scope::addGlobalEventProcessor(static function (Event $event): Event { - $client = SentrySdk::getCurrentHub()->getClient(); + $client = SentrySdk::getClient(); $maxContextLines = $client->getOptions()->getContextLines(); $integration = $client->getIntegration(self::class); diff --git a/src/Integration/ModulesIntegration.php b/src/Integration/ModulesIntegration.php index affe1e062..f1234f355 100644 --- a/src/Integration/ModulesIntegration.php +++ b/src/Integration/ModulesIntegration.php @@ -26,7 +26,7 @@ final class ModulesIntegration implements IntegrationInterface public function setupOnce(): void { Scope::addGlobalEventProcessor(static function (Event $event): Event { - $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); + $integration = SentrySdk::getClient()->getIntegration(self::class); // The integration could be bound to a client that is not the one // attached to the current hub. If this is the case, bail out diff --git a/src/Integration/OTLPIntegration.php b/src/Integration/OTLPIntegration.php index e5e04ef8e..184d55b50 100644 --- a/src/Integration/OTLPIntegration.php +++ b/src/Integration/OTLPIntegration.php @@ -56,8 +56,7 @@ public function setupOnce(): void } Scope::registerExternalPropagationContext(static function (): ?array { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); + $integration = SentrySdk::getClient()->getIntegration(self::class); if (!$integration instanceof self) { return null; @@ -193,9 +192,6 @@ private function getLogger(): LoggerInterface return $this->options->getLoggerOrNullLogger(); } - $currentHub = SentrySdk::getCurrentHub(); - $client = $currentHub->getClient(); - - return $client->getOptions()->getLoggerOrNullLogger(); + return SentrySdk::getClient()->getOptions()->getLoggerOrNullLogger(); } } diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index db123b019..98aa4ccb3 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -103,15 +103,13 @@ public function __construct(?RequestFetcherInterface $requestFetcher = null, arr public function setupOnce(): void { Scope::addGlobalEventProcessor(function (Event $event): Event { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); + $client = SentrySdk::getClient(); + $integration = $client->getIntegration(self::class); if ($integration === null) { return $event; } - $client = $currentHub->getClient(); - $this->processEvent($event, $client->getOptions()); return $event; diff --git a/src/Integration/TransactionIntegration.php b/src/Integration/TransactionIntegration.php index 1ed22d753..746584dab 100644 --- a/src/Integration/TransactionIntegration.php +++ b/src/Integration/TransactionIntegration.php @@ -24,7 +24,7 @@ final class TransactionIntegration implements IntegrationInterface public function setupOnce(): void { Scope::addGlobalEventProcessor(static function (Event $event, EventHint $hint): Event { - $integration = SentrySdk::getCurrentHub()->getIntegration(self::class); + $integration = SentrySdk::getClient()->getIntegration(self::class); // The client bound to the current hub, if any, could not have this // integration enabled. If this is the case, bail out diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index 0915d4655..f92b0b305 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -10,7 +10,6 @@ use Sentry\Event; use Sentry\EventId; use Sentry\SentrySdk; -use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Util\Arr; use Sentry\Util\Str; @@ -41,8 +40,9 @@ public function add( ): void { $timestamp = microtime(true); - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); + $isolationScope = SentrySdk::getIsolationScope(); + $client = SentrySdk::getClient($isolationScope); + $scope = Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope); $options = $client->getOptions(); $sdkLogger = $options->getLogger(); @@ -71,7 +71,7 @@ public function add( $formattedMessage = $message; } - $traceData = $this->getTraceData($hub); + $traceData = $this->getTraceData($scope); $traceId = $traceData['trace_id']; $parentSpanId = $traceData['parent_span_id']; @@ -86,20 +86,18 @@ public function add( $log->setAttribute('sentry.sdk.version', $client->getSdkVersion()); } - $hub->configureScope(static function (Scope $scope) use ($log) { - $user = $scope->getUser(); - if ($user !== null) { - if ($user->getId() !== null) { - $log->setAttribute('user.id', $user->getId()); - } - if ($user->getEmail() !== null) { - $log->setAttribute('user.email', $user->getEmail()); - } - if ($user->getUsername() !== null) { - $log->setAttribute('user.name', $user->getUsername()); - } + $user = $scope->getUser(); + if ($user !== null) { + if ($user->getId() !== null) { + $log->setAttribute('user.id', $user->getId()); + } + if ($user->getEmail() !== null) { + $log->setAttribute('user.email', $user->getEmail()); } - }); + if ($user->getUsername() !== null) { + $log->setAttribute('user.name', $user->getUsername()); + } + } if (\count($values)) { $log->setAttribute('sentry.message.template', $message); @@ -190,9 +188,9 @@ public function all(): array /** * @return array{trace_id: string, parent_span_id: string|null} */ - private function getTraceData(HubInterface $hub): array + private function getTraceData(Scope $scope): array { - $span = $hub->getSpan(); + $span = $scope->getSpan(); if ($span !== null) { return [ @@ -201,28 +199,18 @@ private function getTraceData(HubInterface $hub): array ]; } - $traceData = null; - - $hub->configureScope(static function (Scope $scope) use (&$traceData): void { - $externalPropagationContext = Scope::getExternalPropagationContext(); - - if ($externalPropagationContext !== null) { - $traceData = [ - 'trace_id' => $externalPropagationContext['trace_id'], - 'parent_span_id' => $externalPropagationContext['span_id'], - ]; - - return; - } - - $traceData = [ - 'trace_id' => (string) $scope->getPropagationContext()->getTraceId(), - 'parent_span_id' => null, + $externalPropagationContext = Scope::getExternalPropagationContext(); + if ($externalPropagationContext !== null) { + return [ + 'trace_id' => $externalPropagationContext['trace_id'], + 'parent_span_id' => $externalPropagationContext['span_id'], ]; - }); + } - /** @var array{trace_id: string, parent_span_id: string|null} $traceData */ - return $traceData; + return [ + 'trace_id' => (string) $scope->getPropagationContext()->getTraceId(), + 'parent_span_id' => null, + ]; } /** diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php index b58fddd93..f4d9b2336 100644 --- a/src/Metrics/MetricsAggregator.php +++ b/src/Metrics/MetricsAggregator.php @@ -13,7 +13,6 @@ use Sentry\Metrics\Types\GaugeMetric; use Sentry\Metrics\Types\Metric; use Sentry\SentrySdk; -use Sentry\State\HubInterface; use Sentry\State\Scope; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; @@ -52,60 +51,53 @@ public function add( array $attributes, ?Unit $unit ): void { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); - $metricFlushThreshold = null; + $isolationScope = SentrySdk::getIsolationScope(); + $client = SentrySdk::getClient($isolationScope); + $scope = Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope); + $options = $client->getOptions(); + $metricFlushThreshold = $options->getMetricFlushThreshold(); if (!\is_int($value) && !\is_float($value)) { - if ($client !== null) { - $client->getOptions()->getLoggerOrNullLogger()->debug('Metrics value is neither int nor float. Metric will be discarded'); - } + $options->getLoggerOrNullLogger()->debug('Metrics value is neither int nor float. Metric will be discarded'); return; } - if ($client !== null) { - $options = $client->getOptions(); - $metricFlushThreshold = $options->getMetricFlushThreshold(); + if ($options->getEnableMetrics() === false) { + return; + } - if ($options->getEnableMetrics() === false) { - return; - } + $defaultAttributes = [ + 'sentry.environment' => $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, + 'server.address' => $options->getServerName(), + ]; - $defaultAttributes = [ - 'sentry.environment' => $options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, - 'server.address' => $options->getServerName(), - ]; + if ($client instanceof Client) { + $defaultAttributes['sentry.sdk.name'] = $client->getSdkIdentifier(); + $defaultAttributes['sentry.sdk.version'] = $client->getSdkVersion(); + } - if ($client instanceof Client) { - $defaultAttributes['sentry.sdk.name'] = $client->getSdkIdentifier(); - $defaultAttributes['sentry.sdk.version'] = $client->getSdkVersion(); + $user = $scope->getUser(); + if ($user !== null) { + if ($user->getId() !== null) { + $defaultAttributes['user.id'] = $user->getId(); } - - $hub->configureScope(static function (Scope $scope) use (&$defaultAttributes) { - $user = $scope->getUser(); - if ($user !== null) { - if ($user->getId() !== null) { - $defaultAttributes['user.id'] = $user->getId(); - } - if ($user->getEmail() !== null) { - $defaultAttributes['user.email'] = $user->getEmail(); - } - if ($user->getUsername() !== null) { - $defaultAttributes['user.name'] = $user->getUsername(); - } - } - }); - - $release = $options->getRelease(); - if ($release !== null) { - $defaultAttributes['sentry.release'] = $release; + if ($user->getEmail() !== null) { + $defaultAttributes['user.email'] = $user->getEmail(); } + if ($user->getUsername() !== null) { + $defaultAttributes['user.name'] = $user->getUsername(); + } + } - $attributes += $defaultAttributes; + $release = $options->getRelease(); + if ($release !== null) { + $defaultAttributes['sentry.release'] = $release; } - $traceContext = $this->getTraceContext($hub); + $attributes += $defaultAttributes; + + $traceContext = $this->getTraceContext($scope); $traceId = new TraceId($traceContext['trace_id']); $spanId = new SpanId($traceContext['span_id']); @@ -113,12 +105,10 @@ public function add( /** @var Metric $metric */ $metric = new $metricTypeClass($name, $value, $traceId, $spanId, $attributes, microtime(true), $unit); - if ($client !== null) { - $beforeSendMetric = $client->getOptions()->getBeforeSendMetricCallback(); - $metric = $beforeSendMetric($metric); - if ($metric === null) { - return; - } + $beforeSendMetric = $options->getBeforeSendMetricCallback(); + $metric = $beforeSendMetric($metric); + if ($metric === null) { + return; } $metrics = $this->getStorage($metricFlushThreshold); @@ -147,16 +137,14 @@ public function flush(?ClientInterface $client = null, ?Scope $isolationScope = /** * @return array{trace_id: string, span_id: string} */ - private function getTraceContext(HubInterface $hub): array + private function getTraceContext(Scope $scope): array { - $traceContext = null; - - $hub->configureScope(static function (Scope $scope) use (&$traceContext): void { - $traceContext = $scope->getTraceContext(); - }); + $traceContext = $scope->getTraceContext(); - /** @var array{trace_id: string, span_id: string} $traceContext */ - return $traceContext; + return [ + 'trace_id' => $traceContext['trace_id'], + 'span_id' => $traceContext['span_id'], + ]; } /** diff --git a/src/Tracing/PropagationContext.php b/src/Tracing/PropagationContext.php index e2d525d5d..721321d08 100644 --- a/src/Tracing/PropagationContext.php +++ b/src/Tracing/PropagationContext.php @@ -5,7 +5,6 @@ namespace Sentry\Tracing; use Sentry\SentrySdk; -use Sentry\State\Scope; use Sentry\Tracing\Traits\TraceHeaderParserTrait; final class PropagationContext @@ -84,12 +83,10 @@ public function toTraceparent(): string public function toBaggage(): string { if ($this->dynamicSamplingContext === null) { - $hub = SentrySdk::getCurrentHub(); - $options = $hub->getClient()->getOptions(); - - $hub->configureScope(function (Scope $scope) use ($options) { - $this->dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $scope); - }); + $this->dynamicSamplingContext = DynamicSamplingContext::fromOptions( + SentrySdk::getClient()->getOptions(), + SentrySdk::getIsolationScope() + ); } return (string) $this->dynamicSamplingContext; diff --git a/src/Tracing/Span.php b/src/Tracing/Span.php index e8df25b88..faebe8a47 100644 --- a/src/Tracing/Span.php +++ b/src/Tracing/Span.php @@ -6,7 +6,6 @@ use Sentry\EventId; use Sentry\SentrySdk; -use Sentry\State\Scope; /** * This class stores all the information about a span. @@ -299,11 +298,9 @@ public function setStatus(?SpanStatus $status) */ public function setHttpStatus(int $statusCode) { - SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) use ($statusCode) { - $scope->setContext('response', [ - 'status_code' => $statusCode, - ]); - }); + SentrySdk::getIsolationScope()->setContext('response', [ + 'status_code' => $statusCode, + ]); $status = SpanStatus::createFromHttpStatusCode($statusCode); diff --git a/src/Tracing/Traits/TraceHeaderParserTrait.php b/src/Tracing/Traits/TraceHeaderParserTrait.php index 3accc180b..69933f22b 100644 --- a/src/Tracing/Traits/TraceHeaderParserTrait.php +++ b/src/Tracing/Traits/TraceHeaderParserTrait.php @@ -127,8 +127,7 @@ private static function parseSampleRand(DynamicSamplingContext $samplingContext) } } - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); + $client = SentrySdk::getClient(); $client->getOptions()->getLoggerOrNullLogger()->debug( 'Ignoring invalid sentry-sample_rand baggage value because it must be a numeric value in the range [0, 1).', ['sample_rand' => $sampleRand] @@ -139,8 +138,7 @@ private static function parseSampleRand(DynamicSamplingContext $samplingContext) private static function shouldContinueTrace(DynamicSamplingContext $samplingContext): bool { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); + $client = SentrySdk::getClient(); $options = $client->getOptions(); $clientOrgId = $options->getOrgId(); diff --git a/src/UserDataBag.php b/src/UserDataBag.php index 5fecb478c..bff811885 100644 --- a/src/UserDataBag.php +++ b/src/UserDataBag.php @@ -195,13 +195,9 @@ public function setIpAddress(?string $ipAddress): self } if (filter_var($ipAddress, \FILTER_VALIDATE_IP) === false) { - $client = SentrySdk::getCurrentHub()->getClient(); - - if ($client !== null) { - $client->getOptions()->getLoggerOrNullLogger()->debug( - \sprintf('The "%s" value is not a valid IP address.', $ipAddress) - ); - } + SentrySdk::getClient()->getOptions()->getLoggerOrNullLogger()->debug( + \sprintf('The "%s" value is not a valid IP address.', $ipAddress) + ); return $this; } diff --git a/src/functions.php b/src/functions.php index 9d3cd51e9..eea47629f 100644 --- a/src/functions.php +++ b/src/functions.php @@ -20,7 +20,7 @@ use Sentry\Transport\TransportInterface; /** - * Creates a new Client and Hub which will be set as current. + * Creates a new Client and initializes the SDK. * * @param array{ * attach_stacktrace?: bool, @@ -213,7 +213,7 @@ function addBreadcrumb($category, ?string $message = null, array $metadata = [], */ function configureScope(callable $callback): void { - SentrySdk::getCurrentHub()->configureScope($callback); + $callback(SentrySdk::getIsolationScope()); } /** @@ -330,7 +330,7 @@ function startTransaction(TransactionContext $context, array $customSamplingCont */ function trace(callable $trace, SpanContext $context) { - return SentrySdk::getCurrentHub()->withScope(static function (Scope $scope) use ($context, $trace) { + return withIsolationScope(static function (Scope $scope) use ($context, $trace) { $parentSpan = $scope->getSpan(); $span = null; @@ -360,10 +360,9 @@ function trace(callable $trace, SpanContext $context) */ function getOtlpTracesEndpointUrl(): ?string { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); + $client = SentrySdk::getClient(); - $integration = $hub->getIntegration(OTLPIntegration::class); + $integration = $client->getIntegration(OTLPIntegration::class); if ($integration instanceof OTLPIntegration && $integration->getCollectorUrl() !== null) { return $integration->getCollectorUrl(); } @@ -384,27 +383,22 @@ function getOtlpTracesEndpointUrl(): ?string */ function getTraceparent(): string { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); + $client = SentrySdk::getClient(); $options = $client->getOptions(); + $scope = SentrySdk::getIsolationScope(); if ($options->isTracingEnabled()) { - $span = SentrySdk::getCurrentHub()->getSpan(); + $span = $scope->getSpan(); if ($span !== null) { return $span->toTraceparent(); } } - $traceParent = ''; - $hub->configureScope(static function (Scope $scope) use (&$traceParent) { - if ($scope->hasExternalPropagationContext()) { - return; - } - - $traceParent = $scope->getPropagationContext()->toTraceparent(); - }); + if ($scope->hasExternalPropagationContext()) { + return ''; + } - return $traceParent; + return $scope->getPropagationContext()->toTraceparent(); } /** @@ -415,27 +409,22 @@ function getTraceparent(): string */ function getBaggage(): string { - $hub = SentrySdk::getCurrentHub(); - $client = $hub->getClient(); + $client = SentrySdk::getClient(); $options = $client->getOptions(); + $scope = SentrySdk::getIsolationScope(); if ($options->isTracingEnabled()) { - $span = SentrySdk::getCurrentHub()->getSpan(); + $span = $scope->getSpan(); if ($span !== null) { return $span->toBaggage(); } } - $baggage = ''; - $hub->configureScope(static function (Scope $scope) use (&$baggage) { - if ($scope->hasExternalPropagationContext()) { - return; - } - - $baggage = $scope->getPropagationContext()->toBaggage(); - }); + if ($scope->hasExternalPropagationContext()) { + return ''; + } - return $baggage; + return $scope->getPropagationContext()->toBaggage(); } /** @@ -466,10 +455,7 @@ function continueTrace(string $sentryTrace, string $baggage): TransactionContext $propagationContext->setDynamicSamplingContext($dynamicSamplingContext); } - $hub = SentrySdk::getCurrentHub(); - $hub->configureScope(static function (Scope $scope) use ($propagationContext): void { - $scope->setPropagationContext($propagationContext); - }); + SentrySdk::getIsolationScope()->setPropagationContext($propagationContext); return $transactionContext; } @@ -498,9 +484,7 @@ function traceMetrics(): TraceMetrics */ function addFeatureFlag(string $name, bool $result): void { - SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) use ($name, $result) { - $scope->addFeatureFlag($name, $result); - }); + SentrySdk::getIsolationScope()->addFeatureFlag($name, $result); } /** diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 577951b91..e3d163282 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -19,7 +19,6 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; -use Sentry\State\Hub; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\Span; @@ -33,6 +32,7 @@ use Sentry\Util\SentryUid; use function Sentry\addBreadcrumb; +use function Sentry\addFeatureFlag; use function Sentry\captureCheckIn; use function Sentry\captureEvent; use function Sentry\captureException; @@ -516,15 +516,63 @@ public function testWithScope(): void $this->assertSame('foobarbaz', $returnValue); } - public function testConfigureScope(): void + public function testConfigureScopeMutatesCurrentIsolationScopeOnly(): void { - $callbackInvoked = false; + $globalScope = SentrySdk::getGlobalScope(); + $globalScope->setTag('scope', 'global'); + + $isolationScope = new Scope(); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope($isolationScope); + + $callbackScope = null; - configureScope(static function () use (&$callbackInvoked): void { - $callbackInvoked = true; + configureScope(static function (Scope $scope) use (&$callbackScope): void { + $callbackScope = $scope; + $scope->setTag('scope', 'isolation'); }); - $this->assertTrue($callbackInvoked); + $this->assertSame($isolationScope, $callbackScope); + + $isolationEvent = $isolationScope->applyToEvent(Event::createEvent()); + $this->assertNotNull($isolationEvent); + $this->assertSame(['scope' => 'isolation'], $isolationEvent->getTags()); + + $globalEvent = $globalScope->applyToEvent(Event::createEvent()); + $this->assertNotNull($globalEvent); + $this->assertSame(['scope' => 'global'], $globalEvent->getTags()); + } + + public function testAddFeatureFlagMutatesCurrentIsolationScopeOnly(): void + { + $globalScope = SentrySdk::getGlobalScope(); + $globalScope->addFeatureFlag('global-only', true); + + $isolationScope = new Scope(); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope($isolationScope); + + addFeatureFlag('isolation-only', false); + + $isolationEvent = $isolationScope->applyToEvent(Event::createEvent()); + $this->assertNotNull($isolationEvent); + $this->assertSame([ + 'values' => [ + [ + 'flag' => 'isolation-only', + 'result' => false, + ], + ], + ], $isolationEvent->getContexts()['flags']); + + $globalEvent = $globalScope->applyToEvent(Event::createEvent()); + $this->assertNotNull($globalEvent); + $this->assertSame([ + 'values' => [ + [ + 'flag' => 'global-only', + 'result' => true, + ], + ], + ], $globalEvent->getContexts()['flags']); } public function testStartAndEndContext(): void @@ -653,46 +701,58 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void { $transaction = new Transaction(TransactionContext::make()); $transaction->setSampled(true); + $outerScope = SentrySdk::getIsolationScope(); + $outerScope->setSpan($transaction); - SentrySdk::getCurrentHub()->setSpan($transaction); + $this->assertSame($transaction, SentrySdk::getIsolationScope()->getSpan()); - $this->assertSame($transaction, SentrySdk::getCurrentHub()->getSpan()); + $childSpan = null; - trace(function (Scope $scope) use ($transaction) { - $this->assertNotSame($transaction, $scope->getSpan()); + trace(function (Scope $scope) use ($outerScope, $transaction, &$childSpan): void { + $childSpan = $scope->getSpan(); + + $this->assertNotSame($outerScope, $scope); + $this->assertNotSame($transaction, $childSpan); + $this->assertSame($childSpan, SentrySdk::getIsolationScope()->getSpan()); + $this->assertNull($childSpan->getEndTimestamp()); }, new SpanContext()); - $this->assertSame($transaction, SentrySdk::getCurrentHub()->getSpan()); + $this->assertNotNull($childSpan); + $this->assertNotNull($childSpan->getEndTimestamp()); + $this->assertSame($outerScope, SentrySdk::getIsolationScope()); + $this->assertSame($transaction, SentrySdk::getIsolationScope()->getSpan()); try { - trace(static function () { + trace(function (Scope $scope) use ($transaction): void { + $this->assertNotSame($transaction, $scope->getSpan()); + throw new \RuntimeException('Throwing should still restore the previous span'); }, new SpanContext()); } catch (\RuntimeException $e) { - $this->assertSame($transaction, SentrySdk::getCurrentHub()->getSpan()); + $this->assertSame($outerScope, SentrySdk::getIsolationScope()); + $this->assertSame($transaction, SentrySdk::getIsolationScope()->getSpan()); } } public function testTraceDoesntCreateSpanIfTransactionIsNotSampled(): void { - $scope = $this->createMock(Scope::class); - $transaction = new Transaction(TransactionContext::make()); $transaction->setSampled(false); - $scope->expects($this->never()) - ->method('setSpan'); - $scope->expects($this->exactly(3)) - ->method('getSpan') - ->willReturn($transaction); + $outerScope = SentrySdk::getIsolationScope(); + $outerScope->setSpan($transaction); + $callbackScope = null; - SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); + trace(function (Scope $scope) use ($transaction, &$callbackScope): void { + $callbackScope = $scope; - trace(function () use ($transaction) { - $this->assertSame($transaction, SentrySdk::getCurrentHub()->getSpan()); + $this->assertSame($transaction, $scope->getSpan()); + $this->assertSame($transaction, SentrySdk::getIsolationScope()->getSpan()); }, SpanContext::make()); - $this->assertSame($transaction, SentrySdk::getCurrentHub()->getSpan()); + $this->assertNotSame($outerScope, $callbackScope); + $this->assertSame($outerScope, SentrySdk::getIsolationScope()); + $this->assertSame($transaction, SentrySdk::getIsolationScope()->getSpan()); } public function testTraceparentWithTracingDisabled(): void @@ -717,7 +777,7 @@ public function testTraceparentWithTracingEnabled(): void 'traces_sample_rate' => 1.0, ])); - SentrySdk::setCurrentHub(new Hub($client)); + SentrySdk::getGlobalScope()->setClient($client); $spanContext = (new SpanContext()) ->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')) @@ -725,7 +785,7 @@ public function testTraceparentWithTracingEnabled(): void $span = new Span($spanContext); - SentrySdk::getCurrentHub()->setSpan($span); + SentrySdk::getIsolationScope()->setSpan($span); $traceParent = getTraceparent(); @@ -767,12 +827,13 @@ public function testBaggageWithTracingDisabled(): void 'environment' => 'development', ])); - SentrySdk::setCurrentHub(new Hub($client)); + SentrySdk::getGlobalScope()->setClient($client); SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); $baggage = getBaggage(); $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rand=0.25,sentry-release=1.0.0,sentry-environment=development', $baggage); + $this->assertNotNull($propagationContext->getDynamicSamplingContext()); } public function testBaggageWithTracingEnabled(): void @@ -799,7 +860,7 @@ public function testBaggageWithTracingEnabled(): void $span = $transaction->startChild($spanContext); - SentrySdk::getCurrentHub()->setSpan($span); + SentrySdk::getIsolationScope()->setSpan($span); $baggage = getBaggage(); @@ -819,7 +880,7 @@ public function testGetOtlpTracesEndpointUrlFallsBackToDsn(): void 'dsn' => 'https://public@example.com/1', ])); - SentrySdk::setCurrentHub(new Hub($client)); + SentrySdk::getGlobalScope()->setClient($client); $this->assertSame('https://example.com/api/1/integration/otlp/v1/traces/', getOtlpTracesEndpointUrl()); } @@ -838,16 +899,17 @@ public function testGetOtlpTracesEndpointUrlPrefersCollectorUrl(): void 'dsn' => 'https://public@example.com/1', ])); - SentrySdk::setCurrentHub(new Hub($client)); + SentrySdk::getGlobalScope()->setClient($client); $this->assertSame('http://collector:4318/v1/traces', getOtlpTracesEndpointUrl()); } public function testContinueTrace(): void { - $hub = new Hub(new NoOpClient()); + SentrySdk::getGlobalScope()->setClient(new NoOpClient()); - SentrySdk::setCurrentHub($hub); + $scope = new Scope(); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); $transactionContext = continueTrace( '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', @@ -858,17 +920,15 @@ public function testContinueTrace(): void $this->assertSame('566e3688a61d4bc8', (string) $transactionContext->getParentSpanId()); $this->assertTrue($transactionContext->getParentSampled()); - configureScope(function (Scope $scope): void { - $propagationContext = $scope->getPropagationContext(); + $propagationContext = $scope->getPropagationContext(); - $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $propagationContext->getTraceId()); - $this->assertSame('566e3688a61d4bc8', (string) $propagationContext->getParentSpanId()); + $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $propagationContext->getTraceId()); + $this->assertSame('566e3688a61d4bc8', (string) $propagationContext->getParentSpanId()); - $dynamicSamplingContext = $propagationContext->getDynamicSamplingContext(); + $dynamicSamplingContext = $propagationContext->getDynamicSamplingContext(); - $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $dynamicSamplingContext->get('trace_id')); - $this->assertTrue($dynamicSamplingContext->isFrozen()); - }); + $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $dynamicSamplingContext->get('trace_id')); + $this->assertTrue($dynamicSamplingContext->isFrozen()); } public function testContinueTraceWhenOrgMismatch(): void @@ -881,8 +941,10 @@ public function testContinueTraceWhenOrgMismatch(): void 'org_id' => 1, ])); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::getGlobalScope()->setClient($client); + + $scope = new Scope(); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); $transactionContext = continueTrace( '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', @@ -899,14 +961,12 @@ public function testContinueTraceWhenOrgMismatch(): void $this->assertNull($transactionContext->getMetadata()->getDynamicSamplingContext()); $this->assertNotNull($newSampleRand); - configureScope(function (Scope $scope) use ($newTraceId, $newSampleRand): void { - $propagationContext = $scope->getPropagationContext(); + $propagationContext = $scope->getPropagationContext(); - $this->assertSame($newTraceId, (string) $propagationContext->getTraceId()); - $this->assertNull($propagationContext->getParentSpanId()); - $this->assertNull($propagationContext->getDynamicSamplingContext()); - $this->assertSame($newSampleRand, $propagationContext->getSampleRand()); - }); + $this->assertSame($newTraceId, (string) $propagationContext->getTraceId()); + $this->assertNull($propagationContext->getParentSpanId()); + $this->assertNull($propagationContext->getDynamicSamplingContext()); + $this->assertSame($newSampleRand, $propagationContext->getSampleRand()); } public function testContinueTraceWhenOrgMatch(): void @@ -919,8 +979,10 @@ public function testContinueTraceWhenOrgMatch(): void 'org_id' => 1, ])); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::getGlobalScope()->setClient($client); + + $scope = new Scope(); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); $transactionContext = continueTrace( '566e3688a61d4bc888951642d6f14a19-566e3688a61d4bc8-1', @@ -931,17 +993,15 @@ public function testContinueTraceWhenOrgMatch(): void $this->assertSame('566e3688a61d4bc8', (string) $transactionContext->getParentSpanId()); $this->assertTrue($transactionContext->getParentSampled()); - configureScope(function (Scope $scope): void { - $propagationContext = $scope->getPropagationContext(); + $propagationContext = $scope->getPropagationContext(); - $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $propagationContext->getTraceId()); - $this->assertSame('566e3688a61d4bc8', (string) $propagationContext->getParentSpanId()); + $this->assertSame('566e3688a61d4bc888951642d6f14a19', (string) $propagationContext->getTraceId()); + $this->assertSame('566e3688a61d4bc8', (string) $propagationContext->getParentSpanId()); - $dynamicSamplingContext = $propagationContext->getDynamicSamplingContext(); + $dynamicSamplingContext = $propagationContext->getDynamicSamplingContext(); - $this->assertNotNull($dynamicSamplingContext); - $this->assertSame('1', $dynamicSamplingContext->get('org_id')); - }); + $this->assertNotNull($dynamicSamplingContext); + $this->assertSame('1', $dynamicSamplingContext->get('org_id')); } private function setClientAndIsolationScope(ClientInterface $client): Scope diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php index 4f85f4e62..b604f54db 100644 --- a/tests/Logs/LogsAggregatorTest.php +++ b/tests/Logs/LogsAggregatorTest.php @@ -213,6 +213,31 @@ public function testAttributesAreAddedToLogMessage(): void $this->assertSame('my_user', $attributes->get('user.name')->getValue()); } + public function testMergedScopeAttributesAreAddedToLogMessage(): void + { + $client = ClientBuilder::create([ + 'enable_logs' => true, + ])->getClient(); + + SentrySdk::getGlobalScope()->setClient($client); + SentrySdk::getGlobalScope()->setUser(UserDataBag::createFromUserIdentifier('global-user')); + + $spanContext = new SpanContext(); + $spanContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $spanContext->setSpanId(new SpanId('566e3688a61d4bc8')); + SentrySdk::getIsolationScope()->setSpan(new Span($spanContext)); + + $aggregator = new LogsAggregator(); + $aggregator->add(LogLevel::info(), 'Test message'); + + $logs = $aggregator->all(); + $this->assertCount(1, $logs); + + $attributes = $logs[0]->attributes(); + $this->assertSame('global-user', $attributes->get('user.id')->getValue()); + $this->assertSame('566e3688a61d4bc8', $attributes->get('sentry.trace.parent_span_id')->getValue()); + } + public function testUserAttributesCanBeSetManuallyWithDefaultPiiOff(): void { $client = ClientBuilder::create([ diff --git a/tests/Metrics/TraceMetricsTest.php b/tests/Metrics/TraceMetricsTest.php index bb5773699..3d77e3706 100644 --- a/tests/Metrics/TraceMetricsTest.php +++ b/tests/Metrics/TraceMetricsTest.php @@ -18,6 +18,7 @@ use Sentry\State\Hub; use Sentry\State\HubAdapter; use Sentry\State\Scope; +use Sentry\UserDataBag; use function Sentry\traceMetrics; @@ -114,6 +115,19 @@ public function testDoesNotFlushImmediatelyWhenMetricFlushThresholdIsNull(): voi $this->assertCount(2, StubTransport::$events[0]->getMetrics()); } + public function testMergedScopeAttributesAreAddedToMetric(): void + { + SentrySdk::getGlobalScope()->setUser(UserDataBag::createFromUserIdentifier('global-user')); + + traceMetrics()->count('test-count', 2); + traceMetrics()->flush(); + + $this->assertCount(1, StubTransport::$events); + + $metric = StubTransport::$events[0]->getMetrics()[0]; + $this->assertSame('global-user', $metric->getAttributes()->get('user.id')->getValue()); + } + public function testFlushCapturesMetricsWithProvidedClient(): void { $client = $this->createMock(ClientInterface::class); diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index beefa0cb1..76484896f 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -5,7 +5,9 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\ClientInterface; use Sentry\Options; +use Sentry\SentrySdk; use Sentry\State\Scope; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; @@ -113,6 +115,29 @@ public function testToBaggage(): void $this->assertSame('sentry-trace_id=566e3688a61d4bc888951642d6f14a19', $propagationContext->toBaggage()); } + public function testToBaggageUsesCurrentClientAndIsolationScope(): void + { + $propagationContext = PropagationContext::fromDefaults(); + $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); + $propagationContext->setSampleRand(0.25); + + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options([ + 'release' => '1.0.0', + 'environment' => 'development', + ])); + + SentrySdk::getGlobalScope()->setClient($client); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); + + $this->assertSame( + 'sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rand=0.25,sentry-release=1.0.0,sentry-environment=development', + $propagationContext->toBaggage() + ); + } + public function testGetTraceContext(): void { $propagationContext = PropagationContext::fromDefaults(); diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index cf8956172..1de0abf6d 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -5,6 +5,9 @@ namespace Sentry\Tests\Tracing; use PHPUnit\Framework\TestCase; +use Sentry\Event; +use Sentry\SentrySdk; +use Sentry\State\Scope; use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; @@ -73,6 +76,28 @@ public function testStartChild(): void $this->assertSame($spanContext1->getTraceId(), $span2->getTraceId()); } + public function testSetHttpStatusWritesResponseContextToCurrentIsolationScopeOnly(): void + { + $globalScope = SentrySdk::getGlobalScope(); + $globalScope->setContext('response', [ + 'status_code' => 201, + ]); + + $isolationScope = new Scope(); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope($isolationScope); + + $span = new Span(); + $span->setHttpStatus(404); + + $isolationEvent = $isolationScope->applyToEvent(Event::createEvent()); + $this->assertNotNull($isolationEvent); + $this->assertSame(404, $isolationEvent->getContexts()['response']['status_code']); + + $globalEvent = $globalScope->applyToEvent(Event::createEvent()); + $this->assertNotNull($globalEvent); + $this->assertSame(201, $globalEvent->getContexts()['response']['status_code']); + } + /** * @dataProvider toTraceparentDataProvider */