diff --git a/mago.toml b/mago.toml index f8b021c00..b4534de5d 100644 --- a/mago.toml +++ b/mago.toml @@ -10,3 +10,8 @@ excludes = [ [analyzer] baseline = "analysis-baseline.toml" +ignore = [ + { code = "no-value", pattern = "DebugType::getDebugType" }, + "impossible-condition", + "redundant-logical-operation", +] diff --git a/src/Client.php b/src/Client.php index 2bb80c730..7be70bf8c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,6 +10,7 @@ use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; +use Sentry\State\MergedScope; use Sentry\State\Scope; use Sentry\Transport\Result; use Sentry\Transport\TransportInterface; @@ -140,7 +141,7 @@ public function getCspReportUrl(): ?string /** * {@inheritdoc} */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId + public function captureMessage(string $message, ?Severity $level = null, ?MergedScope $scope = null, ?EventHint $hint = null): ?EventId { $event = Event::createEvent(); $event->setMessage($message); @@ -152,7 +153,7 @@ public function captureMessage(string $message, ?Severity $level = null, ?Scope /** * {@inheritdoc} */ - public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId + public function captureException(\Throwable $exception, ?MergedScope $scope = null, ?EventHint $hint = null): ?EventId { $className = \get_class($exception); if ($this->shouldIgnoreException($className)) { @@ -176,7 +177,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E /** * {@inheritdoc} */ - public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId + public function captureEvent(Event $event, ?EventHint $hint = null, ?MergedScope $scope = null): ?EventId { // Client reports don't need to be augmented in the prepareEvent pipeline. if ($event->getType() !== EventType::clientReport()) { @@ -208,7 +209,7 @@ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scop /** * {@inheritdoc} */ - public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId + public function captureLastError(?MergedScope $scope = null, ?EventHint $hint = null): ?EventId { $error = error_get_last(); @@ -277,13 +278,13 @@ public function getSdkVersion(): string /** * Assembles an event and prepares it to be sent of to Sentry. * - * @param Event $event The payload that will be converted to an Event - * @param EventHint|null $hint May contain additional information about the event - * @param Scope|null $scope Optional scope which enriches the Event + * @param Event $event The payload that will be converted to an Event + * @param EventHint|null $hint May contain additional information about the event + * @param MergedScope|null $scope Optional scope which enriches the Event * * @return Event|null The prepared event object or null if it must be discarded */ - private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event + private function prepareEvent(Event $event, ?EventHint $hint = null, ?MergedScope $scope = null): ?Event { if ($hint !== null) { if ($hint->exception !== null && empty($event->getExceptions())) { diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 5d511519f..06bdd07e6 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -5,6 +5,7 @@ namespace Sentry; use Sentry\Integration\IntegrationInterface; +use Sentry\State\MergedScope; use Sentry\State\Scope; use Sentry\Transport\Result; @@ -23,38 +24,38 @@ public function getCspReportUrl(): ?string; /** * Logs a message. * - * @param string $message The message (primary description) for the event - * @param Severity|null $level The level of the message to be sent - * @param Scope|null $scope An optional scope keeping the state - * @param EventHint|null $hint Object that can contain additional information about the event + * @param string $message The message (primary description) for the event + * @param Severity|null $level The level of the message to be sent + * @param MergedScope|null $scope An optional scope keeping the state + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId; + public function captureMessage(string $message, ?Severity $level = null, ?MergedScope $scope = null, ?EventHint $hint = null): ?EventId; /** * Logs an exception. * - * @param \Throwable $exception The exception object - * @param Scope|null $scope An optional scope keeping the state - * @param EventHint|null $hint Object that can contain additional information about the event + * @param \Throwable $exception The exception object + * @param MergedScope|null $scope An optional scope keeping the state + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId; + public function captureException(\Throwable $exception, ?MergedScope $scope = null, ?EventHint $hint = null): ?EventId; /** * Logs the most recent error (obtained with {@link error_get_last}). * - * @param Scope|null $scope An optional scope keeping the state - * @param EventHint|null $hint Object that can contain additional information about the event + * @param MergedScope|null $scope An optional scope keeping the state + * @param EventHint|null $hint Object that can contain additional information about the event */ - public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId; + public function captureLastError(?MergedScope $scope = null, ?EventHint $hint = null): ?EventId; /** * Captures a new event using the provided data. * - * @param Event $event The event being captured - * @param EventHint|null $hint May contain additional information about the event - * @param Scope|null $scope An optional scope keeping the state + * @param Event $event The event being captured + * @param EventHint|null $hint May contain additional information about the event + * @param MergedScope|null $scope An optional scope keeping the state */ - public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId; + public function captureEvent(Event $event, ?EventHint $hint = null, ?MergedScope $scope = null): ?EventId; /** * Returns the integration instance if it is installed on the client. diff --git a/src/Integration/AbstractErrorListenerIntegration.php b/src/Integration/AbstractErrorListenerIntegration.php index 97123b113..36482a987 100644 --- a/src/Integration/AbstractErrorListenerIntegration.php +++ b/src/Integration/AbstractErrorListenerIntegration.php @@ -5,9 +5,10 @@ namespace Sentry\Integration; use Sentry\Event; +use Sentry\EventHint; use Sentry\ExceptionMechanism; use Sentry\State\EventCapturer; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use function Sentry\withIsolationScope; @@ -18,8 +19,10 @@ abstract class AbstractErrorListenerIntegration implements IntegrationInterface */ protected function captureException(\Throwable $exception): void { - withIsolationScope(function (Scope $scope) use ($exception): void { - $scope->addEventProcessor(\Closure::fromCallable([$this, 'addExceptionMechanismToEvent'])); + withIsolationScope(function (IsolationScope $scope) use ($exception): void { + $scope->addEventProcessor(function (Event $event, EventHint $hint): Event { + return $this->addExceptionMechanismToEvent($event); + }); EventCapturer::captureException($exception); }); diff --git a/src/Logs/LogsAggregator.php b/src/Logs/LogsAggregator.php index f92b0b305..91d1372b3 100644 --- a/src/Logs/LogsAggregator.php +++ b/src/Logs/LogsAggregator.php @@ -10,6 +10,8 @@ use Sentry\Event; use Sentry\EventId; use Sentry\SentrySdk; +use Sentry\State\IsolationScope; +use Sentry\State\MergedScope; use Sentry\State\Scope; use Sentry\Util\Arr; use Sentry\Util\Str; @@ -42,7 +44,7 @@ public function add( $isolationScope = SentrySdk::getIsolationScope(); $client = SentrySdk::getClient($isolationScope); - $scope = Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope); + $scope = SentrySdk::getGlobalScope()->merge($isolationScope); $options = $client->getOptions(); $sdkLogger = $options->getLogger(); @@ -162,7 +164,7 @@ public function add( } } - public function flush(?ClientInterface $client = null, ?Scope $isolationScope = null): ?EventId + public function flush(?ClientInterface $client = null, ?IsolationScope $isolationScope = null): ?EventId { $logs = $this->logs; @@ -174,7 +176,7 @@ public function flush(?ClientInterface $client = null, ?Scope $isolationScope = $client = $client ?? SentrySdk::getClient($isolationScope); $event = Event::createLogs()->setLogs($logs->drain()); - return $client->captureEvent($event, null, Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope)); + return $client->captureEvent($event, null, SentrySdk::getGlobalScope()->merge($isolationScope)); } /** @@ -188,7 +190,7 @@ public function all(): array /** * @return array{trace_id: string, parent_span_id: string|null} */ - private function getTraceData(Scope $scope): array + private function getTraceData(MergedScope $scope): array { $span = $scope->getSpan(); diff --git a/src/Metrics/MetricsAggregator.php b/src/Metrics/MetricsAggregator.php index f4d9b2336..8f46eddb5 100644 --- a/src/Metrics/MetricsAggregator.php +++ b/src/Metrics/MetricsAggregator.php @@ -13,7 +13,8 @@ use Sentry\Metrics\Types\GaugeMetric; use Sentry\Metrics\Types\Metric; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; +use Sentry\State\MergedScope; use Sentry\Tracing\SpanId; use Sentry\Tracing\TraceId; use Sentry\Unit; @@ -53,7 +54,7 @@ public function add( ): void { $isolationScope = SentrySdk::getIsolationScope(); $client = SentrySdk::getClient($isolationScope); - $scope = Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope); + $scope = SentrySdk::getGlobalScope()->merge($isolationScope); $options = $client->getOptions(); $metricFlushThreshold = $options->getMetricFlushThreshold(); @@ -119,7 +120,7 @@ public function add( } } - public function flush(?ClientInterface $client = null, ?Scope $isolationScope = null): ?EventId + public function flush(?ClientInterface $client = null, ?IsolationScope $isolationScope = null): ?EventId { $metrics = $this->metrics; @@ -131,13 +132,13 @@ public function flush(?ClientInterface $client = null, ?Scope $isolationScope = $client = $client ?? SentrySdk::getClient($isolationScope); $event = Event::createMetrics()->setMetrics($metrics->drain()); - return $client->captureEvent($event, null, Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope)); + return $client->captureEvent($event, null, SentrySdk::getGlobalScope()->merge($isolationScope)); } /** * @return array{trace_id: string, span_id: string} */ - private function getTraceContext(Scope $scope): array + private function getTraceContext(MergedScope $scope): array { $traceContext = $scope->getTraceContext(); diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index 7197db202..9fd3538bf 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -13,10 +13,10 @@ use Sentry\Event; use Sentry\SentrySdk; use Sentry\State\BreadcrumbRecorder; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; /** - * This Monolog handler logs every message as a {@see Breadcrumb} into the current {@see Scope}, + * This Monolog handler logs every message as a {@see Breadcrumb} into the current {@see IsolationScope}, * to enrich any event sent to Sentry. */ final class BreadcrumbHandler extends AbstractProcessingHandler diff --git a/src/Monolog/ExceptionToSentryIssueHandler.php b/src/Monolog/ExceptionToSentryIssueHandler.php index 4ab13e89d..7998c9c83 100644 --- a/src/Monolog/ExceptionToSentryIssueHandler.php +++ b/src/Monolog/ExceptionToSentryIssueHandler.php @@ -10,7 +10,7 @@ use Monolog\LogRecord; use Psr\Log\LogLevel; use Sentry\State\EventCapturer; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use function Sentry\withIsolationScope; @@ -39,7 +39,7 @@ public function handle($record): bool return false; } - withIsolationScope(function (Scope $scope) use ($record, $exception): void { + withIsolationScope(function (IsolationScope $scope) use ($record, $exception): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); $scope->setExtra('monolog.message', $record['message']); diff --git a/src/Monolog/LogToSentryIssueHandler.php b/src/Monolog/LogToSentryIssueHandler.php index e7c8d9b44..f38f03020 100644 --- a/src/Monolog/LogToSentryIssueHandler.php +++ b/src/Monolog/LogToSentryIssueHandler.php @@ -12,7 +12,7 @@ use Sentry\Event; use Sentry\EventHint; use Sentry\State\EventCapturer; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use function Sentry\withIsolationScope; @@ -66,7 +66,7 @@ protected function doWrite($record): void $hint = new EventHint(); - withIsolationScope(function (Scope $scope) use ($record, $event, $hint): void { + withIsolationScope(function (IsolationScope $scope) use ($record, $event, $hint): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); diff --git a/src/NoOpClient.php b/src/NoOpClient.php index 364b6073e..5d7b8e360 100644 --- a/src/NoOpClient.php +++ b/src/NoOpClient.php @@ -6,7 +6,7 @@ use Sentry\Integration\IntegrationInterface; use Sentry\Serializer\RepresentationSerializer; -use Sentry\State\Scope; +use Sentry\State\MergedScope; use Sentry\Transport\Result; use Sentry\Transport\ResultStatus; @@ -54,22 +54,22 @@ public function getCspReportUrl(): ?string return null; } - public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId + public function captureMessage(string $message, ?Severity $level = null, ?MergedScope $scope = null, ?EventHint $hint = null): ?EventId { return null; } - public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId + public function captureException(\Throwable $exception, ?MergedScope $scope = null, ?EventHint $hint = null): ?EventId { return null; } - public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId + public function captureLastError(?MergedScope $scope = null, ?EventHint $hint = null): ?EventId { return null; } - public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId + public function captureEvent(Event $event, ?EventHint $hint = null, ?MergedScope $scope = null): ?EventId { return null; } diff --git a/src/SentrySdk.php b/src/SentrySdk.php index d01434fec..b273ccc72 100644 --- a/src/SentrySdk.php +++ b/src/SentrySdk.php @@ -6,9 +6,10 @@ use Sentry\Logs\Logs; use Sentry\Metrics\TraceMetrics; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; use Sentry\State\RuntimeContext; use Sentry\State\RuntimeContextManager; -use Sentry\State\Scope; /** * This class is the main entry point for all the most common SDK features. @@ -18,7 +19,7 @@ final class SentrySdk { /** - * @var Scope|null The process-global scope + * @var GlobalScope|null The process-global scope */ private static $globalScope; @@ -46,21 +47,21 @@ public static function init(?ClientInterface $client = null): void self::$runtimeContextManager = new RuntimeContextManager(); } - public static function getGlobalScope(): Scope + public static function getGlobalScope(): GlobalScope { if (self::$globalScope === null) { - self::$globalScope = new Scope(); + self::$globalScope = new GlobalScope(); } return self::$globalScope; } - public static function getIsolationScope(): Scope + public static function getIsolationScope(): IsolationScope { return self::getCurrentRuntimeContext()->getIsolationScope(); } - public static function getClient(?Scope $isolationScope = null): ClientInterface + public static function getClient(?IsolationScope $isolationScope = null): ClientInterface { $client = ($isolationScope ?? self::getIsolationScope())->getClient(); diff --git a/src/State/BreadcrumbRecorder.php b/src/State/BreadcrumbRecorder.php index 37b315725..d3bfe25a4 100644 --- a/src/State/BreadcrumbRecorder.php +++ b/src/State/BreadcrumbRecorder.php @@ -20,7 +20,7 @@ private function __construct() /** * Records the breadcrumb on the given scope if the client configuration allows it. */ - public static function record(ClientInterface $client, Scope $scope, Breadcrumb $breadcrumb): bool + public static function record(ClientInterface $client, IsolationScope $scope, Breadcrumb $breadcrumb): bool { if ($client instanceof NoOpClient) { return false; diff --git a/src/State/EventCapturer.php b/src/State/EventCapturer.php index 2511a0013..6846df9ca 100644 --- a/src/State/EventCapturer.php +++ b/src/State/EventCapturer.php @@ -26,28 +26,28 @@ private function __construct() public static function captureMessage(string $message, ?Severity $level = null, ?EventHint $hint = null): ?EventId { - return self::capture(static function (ClientInterface $client, Scope $captureScope) use ($message, $level, $hint): ?EventId { + return self::capture(static function (ClientInterface $client, MergedScope $captureScope) use ($message, $level, $hint): ?EventId { return $client->captureMessage($message, $level, $captureScope, $hint); }); } public static function captureException(\Throwable $exception, ?EventHint $hint = null): ?EventId { - return self::capture(static function (ClientInterface $client, Scope $captureScope) use ($exception, $hint): ?EventId { + return self::capture(static function (ClientInterface $client, MergedScope $captureScope) use ($exception, $hint): ?EventId { return $client->captureException($exception, $captureScope, $hint); }); } public static function captureEvent(Event $event, ?EventHint $hint = null): ?EventId { - return self::capture(static function (ClientInterface $client, Scope $captureScope) use ($event, $hint): ?EventId { + return self::capture(static function (ClientInterface $client, MergedScope $captureScope) use ($event, $hint): ?EventId { return $client->captureEvent($event, $hint, $captureScope); }); } public static function captureLastError(?EventHint $hint = null): ?EventId { - return self::capture(static function (ClientInterface $client, Scope $captureScope) use ($hint): ?EventId { + return self::capture(static function (ClientInterface $client, MergedScope $captureScope) use ($hint): ?EventId { return $client->captureLastError($captureScope, $hint); }); } @@ -77,7 +77,7 @@ public static function captureCheckIn(string $slug, CheckInStatus $status, $dura ); $event->setCheckIn($checkIn); - self::captureWithScope($client, $isolationScope, static function (ClientInterface $client, Scope $captureScope) use ($event): ?EventId { + self::captureWithScope($client, $isolationScope, static function (ClientInterface $client, MergedScope $captureScope) use ($event): ?EventId { return $client->captureEvent($event, null, $captureScope); }); @@ -85,7 +85,7 @@ public static function captureCheckIn(string $slug, CheckInStatus $status, $dura } /** - * @param callable(ClientInterface, Scope): ?EventId $capture + * @param callable(ClientInterface, MergedScope): ?EventId $capture */ private static function capture(callable $capture): ?EventId { @@ -95,11 +95,11 @@ private static function capture(callable $capture): ?EventId } /** - * @param callable(ClientInterface, Scope): ?EventId $capture + * @param callable(ClientInterface, MergedScope): ?EventId $capture */ - private static function captureWithScope(ClientInterface $client, Scope $isolationScope, callable $capture): ?EventId + private static function captureWithScope(ClientInterface $client, IsolationScope $isolationScope, callable $capture): ?EventId { - $eventId = $capture($client, Scope::mergeScopes(SentrySdk::getGlobalScope(), $isolationScope)); + $eventId = $capture($client, SentrySdk::getGlobalScope()->merge($isolationScope)); $isolationScope->setLastEventId($eventId); return $eventId; diff --git a/src/State/GlobalScope.php b/src/State/GlobalScope.php new file mode 100644 index 000000000..56737284b --- /dev/null +++ b/src/State/GlobalScope.php @@ -0,0 +1,22 @@ +scopeData->merge($scope->scopeData), $scope->getSpan()); + } +} diff --git a/src/State/IsolationScope.php b/src/State/IsolationScope.php new file mode 100644 index 000000000..0aa36183e --- /dev/null +++ b/src/State/IsolationScope.php @@ -0,0 +1,149 @@ +scopeData->setPropagationContext($propagationContext ?? PropagationContext::fromDefaults()); + } + + /** + * Returns the ID of the last captured event. + */ + public function getLastEventId(): ?EventId + { + return $this->lastEventId; + } + + /** + * @internal + */ + public function setLastEventId(?EventId $lastEventId): void + { + $this->lastEventId = $lastEventId; + } + + /** + * Adds a feature flag to the scope. + * + * @return $this + */ + public function addFeatureFlag(string $key, bool $result): self + { + $this->scopeData->addFeatureFlag($key, $result); + + if ($this->span !== null) { + $this->span->setFlag($key, $result); + } + + return $this; + } + + /** + * Clears event payload data from the scope. The client binding and last + * event ID are preserved. + */ + public function clear(): void + { + parent::clear(); + $this->span = null; + } + + /** + * Returns the span that is on the scope. + */ + public function getSpan(): ?Span + { + return $this->span; + } + + /** + * Sets the span on the scope. + * + * @param Span|null $span The span + * + * @return $this + */ + public function setSpan(?Span $span): self + { + $this->span = $span; + + return $this; + } + + /** + * Returns the transaction attached to the scope (if there is one). + */ + public function getTransaction(): ?Transaction + { + if ($this->span !== null) { + return $this->span->getTransaction(); + } + + return null; + } + + public function hasExternalPropagationContext(): bool + { + return $this->span === null && self::getExternalPropagationContext() !== null; + } + + public function getPropagationContext(): PropagationContext + { + return $this->scopeData->getPropagationContext(); + } + + public function setPropagationContext(PropagationContext $propagationContext): self + { + $this->scopeData->setPropagationContext($propagationContext); + + return $this; + } + + /** + * @return array{ + * trace_id: string, + * span_id: string, + * parent_span_id?: string, + * data?: array, + * description?: string, + * op?: string, + * status?: string, + * tags?: array, + * origin?: string + * } + */ + public function getTraceContext(): array + { + if ($this->span !== null) { + return $this->span->getTraceContext(); + } + + return self::getExternalPropagationContext() ?? $this->scopeData->getPropagationContext()->getTraceContext(); + } +} diff --git a/src/State/MergedScope.php b/src/State/MergedScope.php new file mode 100644 index 000000000..ed19ff1ee --- /dev/null +++ b/src/State/MergedScope.php @@ -0,0 +1,211 @@ +scopeData = $scopeData; + $this->span = $span; + } + + /** + * Returns the client bound to this scope. + */ + public function getClient(): ClientInterface + { + return $this->scopeData->getClient(); + } + + /** + * Applies the current context and fingerprint to the event. If the event has + * already some breadcrumbs on it, the ones from this scope won't get merged. + * + * @param Event $event The event object that will be enriched with scope data + */ + public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $options = null): ?Event + { + $event->setFingerprint(array_merge($event->getFingerprint(), $this->scopeData->getFingerprint())); + + if (empty($event->getBreadcrumbs())) { + $event->setBreadcrumb($this->scopeData->getBreadcrumbs()); + } + + if ($this->scopeData->getLevel() !== null) { + $event->setLevel($this->scopeData->getLevel()); + } + + if (!empty($this->scopeData->getTags())) { + $event->setTags(array_merge($this->scopeData->getTags(), $event->getTags())); + } + + if (!empty($this->scopeData->getFlags())) { + $event->setContext('flags', [ + 'values' => array_map(static function (array $flag) { + return [ + 'flag' => key($flag), + 'result' => current($flag), + ]; + }, array_values($this->scopeData->getFlags())), + ]); + } + + if (!empty($this->scopeData->getExtra())) { + $event->setExtra(array_merge($this->scopeData->getExtra(), $event->getExtra())); + } + + $scopeUser = $this->scopeData->getUser(); + if ($scopeUser !== null) { + $user = $event->getUser(); + + if ($user === null) { + $user = $scopeUser; + } else { + $user = (clone $scopeUser)->merge($user); + } + + $event->setUser($user); + } + + /** + * Apply the trace context to errors if there is a Span on the Scope. + * Else fallback to the external propagation context or to the + * propagation context. + * But do not override a trace context already present. + */ + $externalPropagationContext = null; + if ($this->span === null) { + $externalPropagationContext = self::getExternalPropagationContext(); + } + + $traceContext = $this->span !== null + ? $this->span->getTraceContext() + : ($externalPropagationContext ?? $this->scopeData->getPropagationContext()->getTraceContext()); + + if (!\array_key_exists('trace', $event->getContexts())) { + $event->setContext('trace', $traceContext); + } + + if ($this->span !== null) { + // Apply the dynamic sampling context to errors if there is a Transaction on the Scope + $transaction = $this->span->getTransaction(); + if ($transaction !== null) { + $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); + } + } elseif ($externalPropagationContext === null) { + $dynamicSamplingContext = $this->scopeData->getPropagationContext()->getDynamicSamplingContext(); + if ($dynamicSamplingContext === null && $options !== null) { + $dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $this); + } + $event->setSdkMetadata('dynamic_sampling_context', $dynamicSamplingContext); + } + + foreach (array_merge($this->scopeData->getContexts(), $event->getContexts()) as $name => $data) { + $event->setContext($name, $data); + } + + // We create a empty `EventHint` instance to allow processors to always receive a `EventHint` instance even if there wasn't one + if ($hint === null) { + $hint = new EventHint(); + } + + if ($event->getType() === EventType::event() || $event->getType() === EventType::transaction()) { + if (empty($event->getAttachments())) { + $event->setAttachments($this->scopeData->getAttachments()); + } + } + + foreach (array_merge(parent::$globalEventProcessors, $this->scopeData->getEventProcessors()) as $processor) { + $event = $processor($event, $hint); + + if ($event === null) { + return null; + } + + if (!$event instanceof Event) { + throw new \InvalidArgumentException(\sprintf('The event processor must return null or an instance of the %s class', Event::class)); + } + } + + return $event; + } + + /** + * Returns the span that is on the scope. + */ + public function getSpan(): ?Span + { + return $this->span; + } + + /** + * Returns the transaction attached to the scope (if there is one). + */ + public function getTransaction(): ?Transaction + { + if ($this->span !== null) { + return $this->span->getTransaction(); + } + + return null; + } + + public function hasExternalPropagationContext(): bool + { + return $this->span === null && self::getExternalPropagationContext() !== null; + } + + /** + * @return array{ + * trace_id: string, + * span_id: string, + * parent_span_id?: string, + * data?: array, + * description?: string, + * op?: string, + * status?: string, + * tags?: array, + * origin?: string + * } + */ + public function getTraceContext(): array + { + if ($this->span !== null) { + return $this->span->getTraceContext(); + } + + return self::getExternalPropagationContext() ?? $this->scopeData->getPropagationContext()->getTraceContext(); + } + + public function getPropagationContext(): PropagationContext + { + return $this->scopeData->getPropagationContext(); + } + + public function __clone() + { + $this->scopeData = clone $this->scopeData; + } +} diff --git a/src/State/MutableScope.php b/src/State/MutableScope.php new file mode 100644 index 000000000..5125bd669 --- /dev/null +++ b/src/State/MutableScope.php @@ -0,0 +1,267 @@ +scopeData->getClient(); + } + + /** + * Sets the client bound to this scope. + * + * @return $this + */ + public function setClient(ClientInterface $client): self + { + $this->scopeData->setClient($client); + + return $this; + } + + /** + * Sets a new tag in the tags context. + * + * @param string $key The key that uniquely identifies the tag + * @param string $value The value + * + * @return $this + */ + public function setTag(string $key, string $value): self + { + $this->scopeData->setTag($key, $value); + + return $this; + } + + /** + * Merges the given tags into the current tags context. + * + * @param array $tags The tags to merge into the current context + * + * @return $this + */ + public function setTags(array $tags): self + { + $this->scopeData->setTags(array_merge($this->scopeData->getTags(), $tags)); + + return $this; + } + + /** + * Removes a given tag from the tags context. + * + * @param string $key The key that uniquely identifies the tag + * + * @return $this + */ + public function removeTag(string $key): self + { + $this->scopeData->removeTag($key); + + return $this; + } + + /** + * Sets data to the context by a given name. + * + * @param string $name The name that uniquely identifies the context + * @param array $value The value + * + * @return $this + */ + public function setContext(string $name, array $value): self + { + if (!empty($value)) { + $this->scopeData->setContext($name, $value); + } + + return $this; + } + + /** + * Removes the context from the scope. + * + * @param string $name The name that uniquely identifies the context + * + * @return $this + */ + public function removeContext(string $name): self + { + $this->scopeData->removeContext($name); + + return $this; + } + + /** + * Sets a new information in the extra context. + * + * @param string $key The key that uniquely identifies the information + * @param mixed $value The value + * + * @return $this + */ + public function setExtra(string $key, $value): self + { + $this->scopeData->setExtra($key, $value); + + return $this; + } + + /** + * Merges the given data into the current extras context. + * + * @param array $extras Data to merge into the current context + * + * @return $this + */ + public function setExtras(array $extras): self + { + $this->scopeData->setExtras(array_merge($this->scopeData->getExtra(), $extras)); + + return $this; + } + + /** + * Merges the given data in the user context. + * + * @param array|UserDataBag $user The user data + * + * @return $this + */ + public function setUser($user): self + { + $this->scopeData->setUser($user); + + return $this; + } + + /** + * Removes all data of the user context. + * + * @return $this + */ + public function removeUser(): self + { + $this->scopeData->removeUser(); + + return $this; + } + + /** + * Sets the list of strings used to dictate the deduplication of this event. + * + * @param string[] $fingerprint The fingerprint values + * + * @return $this + */ + public function setFingerprint(array $fingerprint): self + { + $this->scopeData->setFingerprint($fingerprint); + + return $this; + } + + /** + * Sets the severity to apply to all events captured in this scope. + * + * @param Severity|null $level The severity + * + * @return $this + */ + public function setLevel(?Severity $level): self + { + $this->scopeData->setLevel($level); + + return $this; + } + + /** + * Add the given breadcrumb to the scope. + * + * @param Breadcrumb $breadcrumb The breadcrumb to add + * @param int $maxBreadcrumbs The maximum number of breadcrumbs to record + * + * @return $this + */ + public function addBreadcrumb(Breadcrumb $breadcrumb, int $maxBreadcrumbs = 100): self + { + $this->scopeData->addBreadcrumb($breadcrumb, $maxBreadcrumbs); + + return $this; + } + + /** + * Clears all the breadcrumbs. + * + * @return $this + */ + public function clearBreadcrumbs(): self + { + $this->scopeData->clearBreadcrumbs(); + + return $this; + } + + /** + * Adds a new event processor that will be called after {@see MutableScope::applyToEvent} + * finished its work. + * + * @param callable(Event, EventHint): ?Event $eventProcessor The event processor + * + * @return $this + */ + public function addEventProcessor(callable $eventProcessor): self + { + $this->scopeData->addEventProcessor($eventProcessor); + + return $this; + } + + /** + * Clears event payload data from the scope. The client binding and last + * event ID are preserved. + */ + public function clear(): void + { + $this->scopeData->clear(); + } + + public function __clone() + { + $this->scopeData = clone $this->scopeData; + } + + public function addAttachment(Attachment $attachment): self + { + $this->scopeData->addAttachment($attachment); + + return $this; + } + + public function clearAttachments(): self + { + $this->scopeData->setAttachments([]); + + return $this; + } +} diff --git a/src/State/RuntimeContext.php b/src/State/RuntimeContext.php index b0dfe177c..dcc097ef9 100644 --- a/src/State/RuntimeContext.php +++ b/src/State/RuntimeContext.php @@ -23,7 +23,7 @@ final class RuntimeContext private $id; /** - * @var Scope + * @var IsolationScope */ private $isolationScope; @@ -37,10 +37,10 @@ final class RuntimeContext */ private $metricsAggregator; - public function __construct(string $id, ?Scope $isolationScope = null) + public function __construct(string $id, ?IsolationScope $isolationScope = null) { $this->id = $id; - $this->isolationScope = $isolationScope ?? new Scope(); + $this->isolationScope = $isolationScope ?? new IsolationScope(); $this->logsAggregator = new LogsAggregator(); $this->metricsAggregator = new MetricsAggregator(); } @@ -50,12 +50,12 @@ public function getId(): string return $this->id; } - public function getIsolationScope(): Scope + public function getIsolationScope(): IsolationScope { return $this->isolationScope; } - public function setIsolationScope(Scope $isolationScope): void + public function setIsolationScope(IsolationScope $isolationScope): void { $this->isolationScope = $isolationScope; } diff --git a/src/State/Scope.php b/src/State/Scope.php index 0a53b3686..90ce93938 100644 --- a/src/State/Scope.php +++ b/src/State/Scope.php @@ -4,28 +4,17 @@ namespace Sentry\State; -use Sentry\Attachment\Attachment; -use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\Event; use Sentry\EventHint; -use Sentry\EventId; -use Sentry\EventType; use Sentry\NoOpClient; -use Sentry\Options; -use Sentry\Severity; -use Sentry\Tracing\DynamicSamplingContext; -use Sentry\Tracing\PropagationContext; -use Sentry\Tracing\Span; -use Sentry\Tracing\Transaction; use Sentry\UserDataBag; -use Sentry\Util\DebugType; /** * The scope holds data that should implicitly be sent with Sentry events. It * can hold context data, extra parameters, level overrides, fingerprints etc. */ -class Scope +abstract class Scope { /** * Maximum number of flags allowed. We only track the first flags set. @@ -35,126 +24,28 @@ class Scope public const MAX_FLAGS = 100; /** - * @var PropagationContext - */ - private $propagationContext; - - /** - * @var ClientInterface The client bound to this scope - */ - private $client; - - /** - * @var EventId|null The ID of the last captured event - */ - private $lastEventId; - - /** - * @var Breadcrumb[] The list of breadcrumbs recorded in this scope - */ - private $breadcrumbs = []; - - /** - * @var UserDataBag|null The user data associated to this scope - */ - private $user; - - /** - * @var array> The list of contexts associated to this scope - */ - private $contexts = []; - - /** - * @var array The list of tags associated to this scope - */ - private $tags = []; - - /** - * @var array> The list of flags associated to this scope - */ - private $flags = []; - - /** - * @var array A set of extra data associated to this scope - */ - private $extra = []; - - /** - * @var string[] List of fingerprints used to group events together in - * Sentry - */ - private $fingerprint = []; - - /** - * @var Severity|null The severity to associate to the events captured in - * this scope - */ - private $level; - - /** - * @var callable[] List of event processors + * @internal * - * @phpstan-var array + * @var ScopeData */ - private $eventProcessors = []; - - /** - * @var Span|null Set a Span on the Scope - */ - private $span; - - /** - * @var Attachment[] - */ - private $attachments = []; + protected $scopeData; /** * @var callable[] List of event processors * * @phpstan-var array */ - private static $globalEventProcessors = []; + protected static $globalEventProcessors = []; /** * @var callable|null */ - private static $externalPropagationContextCallback; - - public function __construct(?PropagationContext $propagationContext = null) - { - $this->propagationContext = $propagationContext ?? PropagationContext::fromDefaults(); - $this->client = new NoOpClient(); - } + protected static $externalPropagationContextCallback; - /** - * Merges the process-global scope underneath the current isolation scope. - * - * The returned scope is transient and should be used for one event capture. - * - * @internal - */ - public static function mergeScopes(self $globalScope, self $isolationScope): self + public function __construct() { - $mergedScope = clone $isolationScope; - - $mergedScope->tags = array_merge($globalScope->tags, $isolationScope->tags); - $mergedScope->extra = array_merge($globalScope->extra, $isolationScope->extra); - $mergedScope->contexts = array_merge($globalScope->contexts, $isolationScope->contexts); - - if ($globalScope->user !== null && $isolationScope->user !== null) { - $mergedScope->user = (clone $globalScope->user)->merge($isolationScope->user); - } elseif ($globalScope->user !== null) { - $mergedScope->user = clone $globalScope->user; - } - - $mergedScope->level = $isolationScope->level ?? $globalScope->level; - $mergedScope->fingerprint = array_merge($globalScope->fingerprint, $isolationScope->fingerprint); - $mergedScope->breadcrumbs = \array_slice(array_merge($globalScope->breadcrumbs, $isolationScope->breadcrumbs), -100); - $mergedScope->flags = self::mergeFlags($globalScope->flags, $isolationScope->flags); - $mergedScope->attachments = array_merge($globalScope->attachments, $isolationScope->attachments); - $mergedScope->eventProcessors = array_merge($globalScope->eventProcessors, $isolationScope->eventProcessors); - - return $mergedScope; + $this->scopeData = new ScopeData(); + $this->scopeData->setClient(new NoOpClient()); } /** @@ -162,199 +53,7 @@ public static function mergeScopes(self $globalScope, self $isolationScope): sel */ public function getClient(): ClientInterface { - return $this->client; - } - - /** - * Sets the client bound to this scope. - * - * @return $this - */ - public function setClient(ClientInterface $client): self - { - $this->client = $client; - - return $this; - } - - /** - * Returns the ID of the last captured event. - */ - public function getLastEventId(): ?EventId - { - return $this->lastEventId; - } - - /** - * @internal - */ - public function setLastEventId(?EventId $lastEventId): void - { - $this->lastEventId = $lastEventId; - } - - /** - * @param array> $globalFlags - * @param array> $isolationFlags - * - * @return array> - */ - private static function mergeFlags(array $globalFlags, array $isolationFlags): array - { - $flagsByKey = []; - - foreach (array_merge($globalFlags, $isolationFlags) as $flag) { - $flagKey = key($flag); - - if ($flagKey === null) { - continue; - } - - unset($flagsByKey[$flagKey]); - $flagsByKey[$flagKey] = current($flag); - } - - $flagsByKey = \array_slice($flagsByKey, -self::MAX_FLAGS, self::MAX_FLAGS, true); - - $flags = []; - - foreach ($flagsByKey as $flagKey => $flagResult) { - $flags[] = [$flagKey => $flagResult]; - } - - return $flags; - } - - /** - * Sets a new tag in the tags context. - * - * @param string $key The key that uniquely identifies the tag - * @param string $value The value - * - * @return $this - */ - public function setTag(string $key, string $value): self - { - $this->tags[$key] = $value; - - return $this; - } - - /** - * Merges the given tags into the current tags context. - * - * @param array $tags The tags to merge into the current context - * - * @return $this - */ - public function setTags(array $tags): self - { - $this->tags = array_merge($this->tags, $tags); - - return $this; - } - - /** - * Removes a given tag from the tags context. - * - * @param string $key The key that uniquely identifies the tag - * - * @return $this - */ - public function removeTag(string $key): self - { - unset($this->tags[$key]); - - return $this; - } - - /** - * Adds a feature flag to the scope. - * - * @return $this - */ - public function addFeatureFlag(string $key, bool $result): self - { - // If the flag was already set, remove it first - // This basically mimics an LRU cache so that the most recently added flags are kept - foreach ($this->flags as $flagIndex => $flag) { - if (isset($flag[$key])) { - unset($this->flags[$flagIndex]); - } - } - - // Keep only the most recent MAX_FLAGS flags - if (\count($this->flags) >= self::MAX_FLAGS) { - array_shift($this->flags); - } - - $this->flags[] = [$key => $result]; - - if ($this->span !== null) { - $this->span->setFlag($key, $result); - } - - return $this; - } - - /** - * Sets data to the context by a given name. - * - * @param string $name The name that uniquely identifies the context - * @param array $value The value - * - * @return $this - */ - public function setContext(string $name, array $value): self - { - if (!empty($value)) { - $this->contexts[$name] = $value; - } - - return $this; - } - - /** - * Removes the context from the scope. - * - * @param string $name The name that uniquely identifies the context - * - * @return $this - */ - public function removeContext(string $name): self - { - unset($this->contexts[$name]); - - return $this; - } - - /** - * Sets a new information in the extra context. - * - * @param string $key The key that uniquely identifies the information - * @param mixed $value The value - * - * @return $this - */ - public function setExtra(string $key, $value): self - { - $this->extra[$key] = $value; - - return $this; - } - - /** - * Merges the given data into the current extras context. - * - * @param array $extras Data to merge into the current context - * - * @return $this - */ - public function setExtras(array $extras): self - { - $this->extra = array_merge($this->extra, $extras); - - return $this; + return $this->scopeData->getClient(); } /** @@ -362,120 +61,11 @@ public function setExtras(array $extras): self */ public function getUser(): ?UserDataBag { - return $this->user; - } - - /** - * Merges the given data in the user context. - * - * @param array|UserDataBag $user The user data - * - * @return $this - */ - public function setUser($user): self - { - if (!\is_array($user) && !$user instanceof UserDataBag) { - throw new \TypeError(\sprintf('The $user argument must be either an array or an instance of the "%s" class. Got: "%s".', UserDataBag::class, DebugType::getDebugType($user))); - } - - if (\is_array($user)) { - $user = UserDataBag::createFromArray($user); - } - - if ($this->user === null) { - $this->user = $user; - } else { - $this->user = $this->user->merge($user); - } - - return $this; - } - - /** - * Removes all data of the user context. - * - * @return $this - */ - public function removeUser(): self - { - $this->user = null; - - return $this; - } - - /** - * Sets the list of strings used to dictate the deduplication of this event. - * - * @param string[] $fingerprint The fingerprint values - * - * @return $this - */ - public function setFingerprint(array $fingerprint): self - { - $this->fingerprint = $fingerprint; - - return $this; - } - - /** - * Sets the severity to apply to all events captured in this scope. - * - * @param Severity|null $level The severity - * - * @return $this - */ - public function setLevel(?Severity $level): self - { - $this->level = $level; - - return $this; + return $this->scopeData->getUser(); } /** - * Add the given breadcrumb to the scope. - * - * @param Breadcrumb $breadcrumb The breadcrumb to add - * @param int $maxBreadcrumbs The maximum number of breadcrumbs to record - * - * @return $this - */ - public function addBreadcrumb(Breadcrumb $breadcrumb, int $maxBreadcrumbs = 100): self - { - $this->breadcrumbs[] = $breadcrumb; - $this->breadcrumbs = \array_slice($this->breadcrumbs, -$maxBreadcrumbs); - - return $this; - } - - /** - * Clears all the breadcrumbs. - * - * @return $this - */ - public function clearBreadcrumbs(): self - { - $this->breadcrumbs = []; - - return $this; - } - - /** - * Adds a new event processor that will be called after {@see Scope::applyToEvent} - * finished its work. - * - * @param callable $eventProcessor The event processor - * - * @return $this - */ - public function addEventProcessor(callable $eventProcessor): self - { - $this->eventProcessors[] = $eventProcessor; - - return $this; - } - - /** - * Adds a new event processor that will be called after {@see Scope::applyToEvent} + * Adds a new event processor that will be called after {@see MergedScope::applyToEvent} * finished its work. * * @param callable $eventProcessor The event processor @@ -531,235 +121,4 @@ public static function getExternalPropagationContext(): ?array 'span_id' => $spanId, ]; } - - /** - * Clears event payload data from the scope. The client binding and last - * event ID are preserved. - * - * @return $this - */ - public function clear(): self - { - $this->user = null; - $this->level = null; - $this->span = null; - $this->fingerprint = []; - $this->breadcrumbs = []; - $this->tags = []; - $this->flags = []; - $this->extra = []; - $this->contexts = []; - $this->attachments = []; - - return $this; - } - - /** - * Applies the current context and fingerprint to the event. If the event has - * already some breadcrumbs on it, the ones from this scope won't get merged. - * - * @param Event $event The event object that will be enriched with scope data - */ - public function applyToEvent(Event $event, ?EventHint $hint = null, ?Options $options = null): ?Event - { - $event->setFingerprint(array_merge($event->getFingerprint(), $this->fingerprint)); - - if (empty($event->getBreadcrumbs())) { - $event->setBreadcrumb($this->breadcrumbs); - } - - if ($this->level !== null) { - $event->setLevel($this->level); - } - - if (!empty($this->tags)) { - $event->setTags(array_merge($this->tags, $event->getTags())); - } - - if (!empty($this->flags)) { - $event->setContext('flags', [ - 'values' => array_map(static function (array $flag) { - return [ - 'flag' => key($flag), - 'result' => current($flag), - ]; - }, array_values($this->flags)), - ]); - } - - if (!empty($this->extra)) { - $event->setExtra(array_merge($this->extra, $event->getExtra())); - } - - if ($this->user !== null) { - $user = $event->getUser(); - - if ($user === null) { - $user = $this->user; - } else { - $user = $this->user->merge($user); - } - - $event->setUser($user); - } - - /** - * Apply the trace context to errors if there is a Span on the Scope. - * Else fallback to the external propagation context or to the - * propagation context. - * But do not override a trace context already present. - */ - $externalPropagationContext = null; - if ($this->span === null) { - $externalPropagationContext = self::getExternalPropagationContext(); - } - - $traceContext = $this->span !== null - ? $this->span->getTraceContext() - : ($externalPropagationContext ?? $this->propagationContext->getTraceContext()); - - if (!\array_key_exists('trace', $event->getContexts())) { - $event->setContext('trace', $traceContext); - } - - if ($this->span !== null) { - // Apply the dynamic sampling context to errors if there is a Transaction on the Scope - $transaction = $this->span->getTransaction(); - if ($transaction !== null) { - $event->setSdkMetadata('dynamic_sampling_context', $transaction->getDynamicSamplingContext()); - } - } elseif ($externalPropagationContext === null) { - $dynamicSamplingContext = $this->propagationContext->getDynamicSamplingContext(); - if ($dynamicSamplingContext === null && $options !== null) { - $dynamicSamplingContext = DynamicSamplingContext::fromOptions($options, $this); - } - $event->setSdkMetadata('dynamic_sampling_context', $dynamicSamplingContext); - } - - foreach (array_merge($this->contexts, $event->getContexts()) as $name => $data) { - $event->setContext($name, $data); - } - - // We create a empty `EventHint` instance to allow processors to always receive a `EventHint` instance even if there wasn't one - if ($hint === null) { - $hint = new EventHint(); - } - - if ($event->getType() === EventType::event() || $event->getType() === EventType::transaction()) { - if (empty($event->getAttachments())) { - $event->setAttachments($this->attachments); - } - } - - foreach (array_merge(self::$globalEventProcessors, $this->eventProcessors) as $processor) { - $event = $processor($event, $hint); - - if ($event === null) { - return null; - } - - if (!$event instanceof Event) { - throw new \InvalidArgumentException(\sprintf('The event processor must return null or an instance of the %s class', Event::class)); - } - } - - return $event; - } - - /** - * Returns the span that is on the scope. - */ - public function getSpan(): ?Span - { - return $this->span; - } - - /** - * Sets the span on the scope. - * - * @param Span|null $span The span - * - * @return $this - */ - public function setSpan(?Span $span): self - { - $this->span = $span; - - return $this; - } - - /** - * Returns the transaction attached to the scope (if there is one). - */ - public function getTransaction(): ?Transaction - { - if ($this->span !== null) { - return $this->span->getTransaction(); - } - - return null; - } - - public function hasExternalPropagationContext(): bool - { - return $this->span === null && self::getExternalPropagationContext() !== null; - } - - /** - * @return array{ - * trace_id: string, - * span_id: string, - * parent_span_id?: string, - * data?: array, - * description?: string, - * op?: string, - * status?: string, - * tags?: array, - * origin?: string - * } - */ - public function getTraceContext(): array - { - if ($this->span !== null) { - return $this->span->getTraceContext(); - } - - return self::getExternalPropagationContext() ?? $this->propagationContext->getTraceContext(); - } - - public function getPropagationContext(): PropagationContext - { - return $this->propagationContext; - } - - public function setPropagationContext(PropagationContext $propagationContext): self - { - $this->propagationContext = $propagationContext; - - return $this; - } - - public function __clone() - { - if ($this->user !== null) { - $this->user = clone $this->user; - } - if ($this->propagationContext !== null) { - $this->propagationContext = clone $this->propagationContext; - } - } - - public function addAttachment(Attachment $attachment): self - { - $this->attachments[] = $attachment; - - return $this; - } - - public function clearAttachments(): self - { - $this->attachments = []; - - return $this; - } } diff --git a/src/State/ScopeData.php b/src/State/ScopeData.php new file mode 100644 index 000000000..eab5336a3 --- /dev/null +++ b/src/State/ScopeData.php @@ -0,0 +1,405 @@ +> The list of contexts associated to this scope + */ + private $contexts = []; + + /** + * @var array The list of tags associated to this scope + */ + private $tags = []; + + /** + * @var array> The list of flags associated to this scope + */ + private $flags = []; + + /** + * @var array A set of extra data associated to this scope + */ + private $extra = []; + + /** + * @var string[] List of fingerprints used to group events together in + * Sentry + */ + private $fingerprint = []; + + /** + * @var Severity|null The severity to associate to the events captured in + * this scope + */ + private $level; + + /** + * @var callable[] List of event processors + * + * @phpstan-var array + */ + private $eventProcessors = []; + + /** + * @var Attachment[] + */ + private $attachments = []; + + public function __construct(?PropagationContext $propagationContext = null) + { + $this->propagationContext = $propagationContext ?? PropagationContext::fromDefaults(); + } + + public function getPropagationContext(): PropagationContext + { + return $this->propagationContext; + } + + public function setPropagationContext(PropagationContext $propagationContext): void + { + $this->propagationContext = $propagationContext; + } + + public function getClient(): ClientInterface + { + return $this->client; + } + + public function setClient(ClientInterface $client): void + { + $this->client = $client; + } + + /** + * @return Breadcrumb[] + */ + public function getBreadcrumbs(): array + { + return $this->breadcrumbs; + } + + public function addBreadcrumb(Breadcrumb $breadcrumb, int $maxBreadcrumbs = 100): void + { + $this->breadcrumbs[] = $breadcrumb; + $this->breadcrumbs = \array_slice($this->breadcrumbs, -$maxBreadcrumbs); + } + + public function clearBreadcrumbs(): void + { + $this->breadcrumbs = []; + } + + public function getUser(): ?UserDataBag + { + return $this->user; + } + + /** + * @param array|UserDataBag $user + */ + public function setUser($user): void + { + if (!\is_array($user) && !$user instanceof UserDataBag) { + throw new \TypeError(\sprintf('The $user argument must be either an array or an instance of the "%s" class. Got: "%s".', UserDataBag::class, DebugType::getDebugType($user))); + } + + if (\is_array($user)) { + $user = UserDataBag::createFromArray($user); + } + + if ($this->user === null) { + $this->user = $user; + } else { + $this->user = $this->user->merge($user); + } + } + + public function removeUser(): void + { + $this->user = null; + } + + /** + * @return array> + */ + public function getContexts(): array + { + return $this->contexts; + } + + /** + * @param array $value + */ + public function setContext(string $name, array $value): void + { + $this->contexts[$name] = $value; + } + + public function removeContext(string $key): void + { + unset($this->contexts[$key]); + } + + /** + * @return array + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * @param array $tags + */ + public function setTags(array $tags): void + { + $this->tags = $tags; + } + + public function setTag(string $key, string $value): void + { + $this->tags[$key] = $value; + } + + public function removeTag(string $key): void + { + unset($this->tags[$key]); + } + + /** + * @return array> + */ + public function getFlags(): array + { + return $this->flags; + } + + public function addFeatureFlag(string $key, bool $result): self + { + // If the flag was already set, remove it first + // This basically mimics an LRU cache so that the most recently added flags are kept + foreach ($this->flags as $flagIndex => $flag) { + if (isset($flag[$key])) { + unset($this->flags[$flagIndex]); + } + } + + // Keep only the most recent MAX_FLAGS flags + if (\count($this->flags) >= Scope::MAX_FLAGS) { + array_shift($this->flags); + } + + $this->flags[] = [$key => $result]; + + return $this; + } + + /** + * @return array + */ + public function getExtra(): array + { + return $this->extra; + } + + /** + * @param array $extra + */ + public function setExtras(array $extra): void + { + $this->extra = $extra; + } + + /** + * @param mixed $value + */ + public function setExtra(string $key, $value): void + { + $this->extra[$key] = $value; + } + + /** + * @return string[] + */ + public function getFingerprint(): array + { + return $this->fingerprint; + } + + /** + * @param string[] $fingerprint + */ + public function setFingerprint(array $fingerprint): void + { + $this->fingerprint = $fingerprint; + } + + public function getLevel(): ?Severity + { + return $this->level; + } + + public function setLevel(?Severity $level): void + { + $this->level = $level; + } + + /** + * @return array + */ + public function getEventProcessors(): array + { + return $this->eventProcessors; + } + + /** + * @param callable(Event, EventHint): ?Event $eventProcessor + */ + public function addEventProcessor(callable $eventProcessor): void + { + $this->eventProcessors[] = $eventProcessor; + } + + /** + * @return Attachment[] + */ + public function getAttachments(): array + { + return $this->attachments; + } + + /** + * @param Attachment[] $attachments + */ + public function setAttachments(array $attachments): void + { + $this->attachments = $attachments; + } + + public function addAttachment(Attachment $attachment): void + { + $this->attachments[] = $attachment; + } + + public function clear(): void + { + $this->user = null; + $this->level = null; + $this->fingerprint = []; + $this->breadcrumbs = []; + $this->tags = []; + $this->flags = []; + $this->extra = []; + $this->contexts = []; + $this->attachments = []; + } + + public function __clone() + { + if ($this->user !== null) { + $this->user = clone $this->user; + } + $this->propagationContext = clone $this->propagationContext; + } + + /** + * Merges data of one ScopeData object into the other one. Data stored in $other will have precedence over + * $this. It is generally assumed $this refers to the global scope while $other is the isolation scope. + */ + public function merge(self $other): self + { + $merged = clone $other; + $merged->tags = array_merge($this->tags, $other->tags); + $merged->extra = array_merge($this->extra, $other->extra); + $merged->contexts = array_merge($this->contexts, $other->contexts); + + if ($this->user !== null && $other->user !== null) { + $merged->user = (clone $this->user)->merge($other->user); + } elseif ($this->user !== null) { + $merged->user = clone $this->user; + } + + $merged->level = $other->level ?? $this->level; + $merged->fingerprint = array_merge($this->fingerprint, $other->fingerprint); + $merged->breadcrumbs = \array_slice(array_merge($this->breadcrumbs, $other->breadcrumbs), -100); + $merged->flags = self::mergeFlags($this->flags, $other->flags); + $merged->attachments = array_merge($this->attachments, $other->attachments); + $merged->eventProcessors = array_merge($this->eventProcessors, $other->eventProcessors); + + return $merged; + } + + /** + * @param array> $globalFlags + * @param array> $isolationFlags + * + * @return array> + */ + private static function mergeFlags(array $globalFlags, array $isolationFlags): array + { + $flagsByKey = []; + + foreach (array_merge($globalFlags, $isolationFlags) as $flag) { + $flagKey = key($flag); + + if ($flagKey === null) { + continue; + } + + unset($flagsByKey[$flagKey]); + $flagsByKey[$flagKey] = current($flag); + } + + $flagsByKey = \array_slice($flagsByKey, -Scope::MAX_FLAGS, Scope::MAX_FLAGS, true); + + $flags = []; + + foreach ($flagsByKey as $flagKey => $flagResult) { + $flags[] = [$flagKey => $flagResult]; + } + + return $flags; + } +} diff --git a/src/Tracing/DynamicSamplingContext.php b/src/Tracing/DynamicSamplingContext.php index 588eead06..e8bee30bd 100644 --- a/src/Tracing/DynamicSamplingContext.php +++ b/src/Tracing/DynamicSamplingContext.php @@ -6,7 +6,8 @@ use Sentry\ClientInterface; use Sentry\Options; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; +use Sentry\State\MergedScope; /** * This class represents the Dynamic Sampling Context (dsc). @@ -179,7 +180,10 @@ public static function fromTransaction(Transaction $transaction, ClientInterface return $samplingContext; } - public static function fromOptions(Options $options, Scope $scope): self + /** + * @param IsolationScope|MergedScope $scope + */ + public static function fromOptions(Options $options, $scope): self { $samplingContext = new self(); $samplingContext->set('trace_id', (string) $scope->getPropagationContext()->getTraceId()); diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 27cdca32a..50d73466d 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -12,7 +12,7 @@ use Sentry\ClientInterface; use Sentry\SentrySdk; use Sentry\State\BreadcrumbRecorder; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use function Sentry\getBaggage; use function Sentry\getTraceparent; @@ -22,7 +22,7 @@ */ final class GuzzleTracingMiddleware { - public static function trace(?Scope $scope = null): \Closure + public static function trace(?IsolationScope $scope = null): \Closure { return static function (callable $handler) use ($scope): \Closure { return static function (RequestInterface $request, array $options) use ($handler, $scope) { diff --git a/src/functions.php b/src/functions.php index 9db1674d4..bb234f982 100644 --- a/src/functions.php +++ b/src/functions.php @@ -12,6 +12,8 @@ use Sentry\Metrics\TraceMetrics; use Sentry\State\BreadcrumbRecorder; use Sentry\State\EventCapturer; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SpanContext; @@ -79,12 +81,12 @@ function init(array $options = []): void SentrySdk::init($client); } -function getGlobalScope(): Scope +function getGlobalScope(): GlobalScope { return SentrySdk::getGlobalScope(); } -function getIsolationScope(): Scope +function getIsolationScope(): IsolationScope { return SentrySdk::getIsolationScope(); } @@ -244,7 +246,7 @@ function withScope(callable $callback) * * @phpstan-template T * - * @phpstan-param callable(Scope): T $callback + * @phpstan-param callable(IsolationScope): T $callback * * @return mixed|void The callback's return value, upon successful execution * @@ -323,14 +325,14 @@ function startTransaction(TransactionContext $context, array $customSamplingCont * * @template T * - * @param callable(Scope): T $trace The callable that is going to be traced - * @param SpanContext $context The context of the span to be created + * @param callable(IsolationScope): T $trace The callable that is going to be traced + * @param SpanContext $context The context of the span to be created * * @return T */ function trace(callable $trace, SpanContext $context) { - return withIsolationScope(static function (Scope $scope) use ($context, $trace) { + return withIsolationScope(static function (IsolationScope $scope) use ($context, $trace) { $parentSpan = $scope->getSpan(); $span = null; @@ -381,7 +383,7 @@ function getOtlpTracesEndpointUrl(): ?string * This function is context aware, as in it either returns the traceparent based * on the current span, or the scope's propagation context. */ -function getTraceparent(?Scope $scope = null): string +function getTraceparent(?IsolationScope $scope = null): string { $scope = $scope ?? SentrySdk::getIsolationScope(); $client = SentrySdk::getClient($scope); @@ -407,7 +409,7 @@ function getTraceparent(?Scope $scope = null): string * This function is context aware, as in it either returns the baggage based * on the current span or the scope's propagation context. */ -function getBaggage(?Scope $scope = null): string +function getBaggage(?IsolationScope $scope = null): string { $scope = $scope ?? SentrySdk::getIsolationScope(); $client = SentrySdk::getClient($scope); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 6f23fba98..f2c1707e4 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -20,6 +20,8 @@ use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Severity; use Sentry\Stacktrace; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; use Sentry\State\Scope; use Sentry\Transport\Result; use Sentry\Transport\ResultStatus; @@ -69,7 +71,7 @@ public function setupOnce(): void $logger ); - $client->captureEvent(Event::createEvent(), null, new Scope()); + $client->captureEvent(Event::createEvent(), null, (new GlobalScope())->merge(new IsolationScope())); $this->assertTrue($integrationCalled); } @@ -966,12 +968,12 @@ public function testProcessEventDiscardsEventWhenEventProcessorReturnsNull(): vo ->setLogger($logger) ->getClient(); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->addEventProcessor(static function () { return null; }); - $client->captureMessage('foo', Severity::debug(), $scope); + $client->captureMessage('foo', Severity::debug(), (new GlobalScope())->merge($scope)); } public function testAttachStacktrace(): void diff --git a/tests/Fixtures/runtime/frankenphp/index.php b/tests/Fixtures/runtime/frankenphp/index.php index f6f615299..e0367175e 100644 --- a/tests/Fixtures/runtime/frankenphp/index.php +++ b/tests/Fixtures/runtime/frankenphp/index.php @@ -4,7 +4,6 @@ use Sentry\Event; use Sentry\SentrySdk; -use Sentry\State\Scope; use function Sentry\getTraceparent; use function Sentry\init; @@ -50,7 +49,7 @@ } $event = Event::createEvent(); - $event = Scope::mergeScopes(SentrySdk::getGlobalScope(), SentrySdk::getIsolationScope())->applyToEvent($event); + $event = SentrySdk::getGlobalScope()->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $tags = []; diff --git a/tests/Fixtures/runtime/roadrunner-worker.php b/tests/Fixtures/runtime/roadrunner-worker.php index a5cc7c648..f8d6bf79d 100644 --- a/tests/Fixtures/runtime/roadrunner-worker.php +++ b/tests/Fixtures/runtime/roadrunner-worker.php @@ -6,7 +6,6 @@ use Nyholm\Psr7\Response; use Sentry\Event; use Sentry\SentrySdk; -use Sentry\State\Scope; use Spiral\RoadRunner\Http\PSR7Worker; use Spiral\RoadRunner\Worker; @@ -106,7 +105,7 @@ function handleRequest($request): Response } $event = Event::createEvent(); - $event = Scope::mergeScopes(SentrySdk::getGlobalScope(), SentrySdk::getIsolationScope())->applyToEvent($event); + $event = SentrySdk::getGlobalScope()->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $tags = []; diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 6e3984c91..a61baefca 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -19,6 +19,9 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\Severity; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; +use Sentry\State\MergedScope; use Sentry\State\Scope; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\Span; @@ -74,7 +77,7 @@ public function testInitPreservesGlobalScope(): void $this->assertSame($globalScope, SentrySdk::getGlobalScope()); $this->assertSame(SentrySdk::getClient(), $globalScope->getClient()); - $event = $globalScope->applyToEvent(Event::createEvent()); + $event = $globalScope->merge(new IsolationScope())->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['baseline' => 'yes'], $event->getTags()); @@ -365,7 +368,7 @@ public function testWithMonitor(): void && $checkIn->getMonitorConfig() !== null && $checkIn->getMonitorConfig()->getSchedule()->getValue() === '*/5 * * * *'; }), null, $this->captureScopeConstraint($scope)) - ->willReturnCallback(static function (Event $event, ?EventHint $hint = null, ?Scope $scope = null) use (&$events): EventId { + ->willReturnCallback(static function (Event $event, ?EventHint $hint = null, ?MergedScope $scope = null) use (&$events): EventId { $events[] = $event; return EventId::generate(); @@ -396,7 +399,7 @@ public function testWithMonitorCallableThrows(): void $client->expects($this->exactly(2)) ->method('captureEvent') ->with($this->isInstanceOf(Event::class), null, $this->captureScopeConstraint($scope)) - ->willReturnCallback(static function (Event $event, ?EventHint $hint = null, ?Scope $scope = null) use (&$events): EventId { + ->willReturnCallback(static function (Event $event, ?EventHint $hint = null, ?MergedScope $scope = null) use (&$events): EventId { $events[] = $event; return EventId::generate(); @@ -426,7 +429,7 @@ public function testWithMonitorCallableThrows(): void public function testAddBreadcrumb(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $otherScope = new Scope(); + $otherScope = new IsolationScope(); /** @var ClientInterface&MockObject $client */ $client = $this->createMock(ClientInterface::class); @@ -523,23 +526,23 @@ public function testConfigureScopeMutatesCurrentIsolationScopeOnly(): void $globalScope = SentrySdk::getGlobalScope(); $globalScope->setTag('scope', 'global'); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($isolationScope); $callbackScope = null; - configureScope(static function (Scope $scope) use (&$callbackScope): void { + configureScope(static function (IsolationScope $scope) use (&$callbackScope): void { $callbackScope = $scope; $scope->setTag('scope', 'isolation'); }); $this->assertSame($isolationScope, $callbackScope); - $isolationEvent = $isolationScope->applyToEvent(Event::createEvent()); + $isolationEvent = (new GlobalScope())->merge($isolationScope)->applyToEvent(Event::createEvent()); $this->assertNotNull($isolationEvent); $this->assertSame(['scope' => 'isolation'], $isolationEvent->getTags()); - $globalEvent = $globalScope->applyToEvent(Event::createEvent()); + $globalEvent = $globalScope->merge(new IsolationScope())->applyToEvent(Event::createEvent()); $this->assertNotNull($globalEvent); $this->assertSame(['scope' => 'global'], $globalEvent->getTags()); } @@ -547,14 +550,13 @@ public function testConfigureScopeMutatesCurrentIsolationScopeOnly(): void public function testAddFeatureFlagMutatesCurrentIsolationScopeOnly(): void { $globalScope = SentrySdk::getGlobalScope(); - $globalScope->addFeatureFlag('global-only', true); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($isolationScope); addFeatureFlag('isolation-only', false); - $isolationEvent = $isolationScope->applyToEvent(Event::createEvent()); + $isolationEvent = (new GlobalScope())->merge($isolationScope)->applyToEvent(Event::createEvent()); $this->assertNotNull($isolationEvent); $this->assertSame([ 'values' => [ @@ -565,16 +567,9 @@ public function testAddFeatureFlagMutatesCurrentIsolationScopeOnly(): void ], ], $isolationEvent->getContexts()['flags']); - $globalEvent = $globalScope->applyToEvent(Event::createEvent()); + $globalEvent = $globalScope->merge(new IsolationScope())->applyToEvent(Event::createEvent()); $this->assertNotNull($globalEvent); - $this->assertSame([ - 'values' => [ - [ - 'flag' => 'global-only', - 'result' => true, - ], - ], - ], $globalEvent->getContexts()['flags']); + $this->assertArrayNotHasKey('flags', $globalEvent->getContexts()); } public function testStartAndEndContext(): void @@ -621,7 +616,7 @@ public function testNestedWithContextReusesOuterContext(): void withContext(function () use (&$outerScope, &$innerScope, $globalScope): void { $outerScope = SentrySdk::getIsolationScope(); - configureScope(static function (Scope $scope): void { + configureScope(static function (IsolationScope $scope): void { $scope->setTag('outer', 'yes'); }); @@ -631,8 +626,8 @@ public function testNestedWithContextReusesOuterContext(): void $event = Event::createEvent(); - configureScope(static function (Scope $scope) use (&$event): void { - $event = $scope->applyToEvent($event); + configureScope(static function (IsolationScope $scope) use (&$event): void { + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent($event); }); $this->assertNotSame($globalScope, SentrySdk::getIsolationScope()); @@ -710,7 +705,7 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void $childSpan = null; - trace(function (Scope $scope) use ($outerScope, $transaction, &$childSpan): void { + trace(function (IsolationScope $scope) use ($outerScope, $transaction, &$childSpan): void { $childSpan = $scope->getSpan(); $this->assertNotSame($outerScope, $scope); @@ -725,7 +720,7 @@ public function testTraceCorrectlyReplacesAndRestoresCurrentSpan(): void $this->assertSame($transaction, SentrySdk::getIsolationScope()->getSpan()); try { - trace(function (Scope $scope) use ($transaction): void { + trace(function (IsolationScope $scope) use ($transaction): void { $this->assertNotSame($transaction, $scope->getSpan()); throw new \RuntimeException('Throwing should still restore the previous span'); @@ -745,7 +740,7 @@ public function testTraceDoesntCreateSpanIfTransactionIsNotSampled(): void $outerScope->setSpan($transaction); $callbackScope = null; - trace(function (Scope $scope) use ($transaction, &$callbackScope): void { + trace(function (IsolationScope $scope) use ($transaction, &$callbackScope): void { $callbackScope = $scope; $this->assertSame($transaction, $scope->getSpan()); @@ -763,7 +758,7 @@ public function testTraceparentWithTracingDisabled(): void $propagationContext->setTraceId(new TraceId('566e3688a61d4bc888951642d6f14a19')); $propagationContext->setSpanId(new SpanId('566e3688a61d4bc8')); - SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new IsolationScope($propagationContext)); $traceParent = getTraceparent(); @@ -807,7 +802,7 @@ public function testTraceHeadersAreEmptyWhenExternalPropagationContextIsActive() ]; }); - SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new IsolationScope($propagationContext)); $this->assertSame('', getTraceparent()); $this->assertSame('', getBaggage()); @@ -830,7 +825,7 @@ public function testBaggageWithTracingDisabled(): void ])); SentrySdk::getGlobalScope()->setClient($client); - SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new IsolationScope($propagationContext)); $baggage = getBaggage(); @@ -910,7 +905,7 @@ public function testContinueTrace(): void { SentrySdk::getGlobalScope()->setClient(new NoOpClient()); - $scope = new Scope(); + $scope = new IsolationScope(); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); $transactionContext = continueTrace( @@ -945,7 +940,7 @@ public function testContinueTraceWhenOrgMismatch(): void SentrySdk::getGlobalScope()->setClient($client); - $scope = new Scope(); + $scope = new IsolationScope(); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); $transactionContext = continueTrace( @@ -983,7 +978,7 @@ public function testContinueTraceWhenOrgMatch(): void SentrySdk::getGlobalScope()->setClient($client); - $scope = new Scope(); + $scope = new IsolationScope(); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); $transactionContext = continueTrace( @@ -1006,7 +1001,7 @@ public function testContinueTraceWhenOrgMatch(): void $this->assertSame('1', $dynamicSamplingContext->get('org_id')); } - private function setClientAndIsolationScope(ClientInterface $client): Scope + private function setClientAndIsolationScope(ClientInterface $client): IsolationScope { SentrySdk::init(); @@ -1015,7 +1010,7 @@ private function setClientAndIsolationScope(ClientInterface $client): Scope SentrySdk::getGlobalScope()->setTag('global', 'yes'); SentrySdk::getGlobalScope()->setClient($client); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setTag('scope', 'isolation'); $scope->setTag('isolation', 'yes'); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); @@ -1023,9 +1018,9 @@ private function setClientAndIsolationScope(ClientInterface $client): Scope return $scope; } - private function captureScopeConstraint(Scope $isolationScope) + private function captureScopeConstraint(IsolationScope $isolationScope) { - return $this->callback(function (Scope $captureScope) use ($isolationScope): bool { + return $this->callback(function (MergedScope $captureScope) use ($isolationScope): bool { $this->assertNotSame($isolationScope, $captureScope); $event = $captureScope->applyToEvent(Event::createEvent()); @@ -1044,9 +1039,9 @@ private function captureScopeConstraint(Scope $isolationScope) /** * @param Breadcrumb[] $expectedBreadcrumbs */ - private function assertScopeBreadcrumbs(Scope $scope, array $expectedBreadcrumbs): void + private function assertScopeBreadcrumbs(IsolationScope $scope, array $expectedBreadcrumbs): void { - $event = $scope->applyToEvent(Event::createEvent()); + $event = (new GlobalScope())->merge($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame($expectedBreadcrumbs, $event->getBreadcrumbs()); diff --git a/tests/Integration/EnvironmentIntegrationTest.php b/tests/Integration/EnvironmentIntegrationTest.php index 7391acbce..3c7a66fe7 100644 --- a/tests/Integration/EnvironmentIntegrationTest.php +++ b/tests/Integration/EnvironmentIntegrationTest.php @@ -12,7 +12,7 @@ use Sentry\Event; use Sentry\Integration\EnvironmentIntegration; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use Sentry\Util\PHPVersion; use function Sentry\withScope; @@ -35,12 +35,12 @@ public function testInvoke(bool $isIntegrationEnabled, ?RuntimeContext $initialR SentrySdk::init($client); - withScope(function (Scope $scope) use ($expectedRuntimeContext, $expectedOsContext, $initialRuntimeContext, $initialOsContext): void { + withScope(function (IsolationScope $scope) use ($expectedRuntimeContext, $expectedOsContext, $initialRuntimeContext, $initialOsContext): void { $event = Event::createEvent(); $event->setRuntimeContext($initialRuntimeContext); $event->setOsContext($initialOsContext); - $event = $scope->applyToEvent($event); + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent($event); $this->assertNotNull($event); diff --git a/tests/Integration/FrameContextifierIntegrationTest.php b/tests/Integration/FrameContextifierIntegrationTest.php index f13ba8e7d..ac49a4388 100644 --- a/tests/Integration/FrameContextifierIntegrationTest.php +++ b/tests/Integration/FrameContextifierIntegrationTest.php @@ -14,7 +14,7 @@ use Sentry\Options; use Sentry\SentrySdk; use Sentry\Stacktrace; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use function Sentry\withScope; @@ -49,8 +49,8 @@ public function testInvoke(string $fixtureFilePath, int $lineNumber, int $contex $event = Event::createEvent(); $event->setStacktrace($stacktrace); - withScope(static function (Scope $scope) use (&$event): void { - $event = $scope->applyToEvent($event); + withScope(static function (IsolationScope $scope) use (&$event): void { + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent($event); }); $this->assertNotNull($event); @@ -143,8 +143,8 @@ public function testInvokeLogsWarningMessageIfSourceCodeExcerptCannotBeRetrieved $event = Event::createEvent(); $event->setStacktrace($stacktrace); - withScope(static function (Scope $scope) use (&$event): void { - $event = $scope->applyToEvent($event); + withScope(static function (IsolationScope $scope) use (&$event): void { + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent($event); }); $this->assertNotNull($event); diff --git a/tests/Integration/ModulesIntegrationTest.php b/tests/Integration/ModulesIntegrationTest.php index 659a50398..7e2849795 100644 --- a/tests/Integration/ModulesIntegrationTest.php +++ b/tests/Integration/ModulesIntegrationTest.php @@ -11,7 +11,8 @@ use Sentry\Event; use Sentry\Integration\ModulesIntegration; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; use Sentry\Transport\Result; use Sentry\Transport\ResultStatus; use Sentry\Transport\TransportInterface; @@ -36,8 +37,8 @@ public function testInvoke(bool $isIntegrationEnabled, bool $expectedEmptyModule SentrySdk::init($client); - withScope(function (Scope $scope) use ($expectedEmptyModules): void { - $event = $scope->applyToEvent(Event::createEvent()); + withScope(function (IsolationScope $scope) use ($expectedEmptyModules): void { + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); @@ -88,7 +89,7 @@ public function testModuleIntegration(): void SentrySdk::init($client); - $client->captureEvent(Event::createEvent(), null, new Scope()); + $client->captureEvent(Event::createEvent(), null, (new GlobalScope())->merge(new IsolationScope())); } /** diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index c08cc3e1c..7de6c166c 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -16,7 +16,7 @@ use Sentry\Integration\RequestIntegration; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use Sentry\UserDataBag; use function Sentry\withScope; @@ -46,8 +46,8 @@ public function testInvoke(array $options, ServerRequestInterface $request, arra SentrySdk::init($client); - withScope(function (Scope $scope) use ($event, $expectedRequestContextData, $expectedUser): void { - $event = $scope->applyToEvent($event); + withScope(function (IsolationScope $scope) use ($event, $expectedRequestContextData, $expectedUser): void { + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent($event); $this->assertNotNull($event); $this->assertSame($expectedRequestContextData, $event->getRequest()); diff --git a/tests/Integration/TransactionIntegrationTest.php b/tests/Integration/TransactionIntegrationTest.php index 1faf42176..91bced84a 100644 --- a/tests/Integration/TransactionIntegrationTest.php +++ b/tests/Integration/TransactionIntegrationTest.php @@ -11,7 +11,7 @@ use Sentry\EventHint; use Sentry\Integration\TransactionIntegration; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use function Sentry\withScope; @@ -37,8 +37,8 @@ public function testSetupOnce(Event $event, bool $isIntegrationEnabled, ?EventHi SentrySdk::init($client); - withScope(function (Scope $scope) use ($event, $hint, $expectedTransaction): void { - $event = $scope->applyToEvent($event, $hint); + withScope(function (IsolationScope $scope) use ($event, $hint, $expectedTransaction): void { + $event = SentrySdk::getGlobalScope()->merge($scope)->applyToEvent($event, $hint); $this->assertNotNull($event); $this->assertSame($event->getTransaction(), $expectedTransaction); diff --git a/tests/Logs/LogsAggregatorTest.php b/tests/Logs/LogsAggregatorTest.php index a62d0bd47..f2b6a30b5 100644 --- a/tests/Logs/LogsAggregatorTest.php +++ b/tests/Logs/LogsAggregatorTest.php @@ -13,6 +13,7 @@ use Sentry\Logs\LogsAggregator; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\State\IsolationScope; use Sentry\State\Scope; use Sentry\Tests\StubTransport; use Sentry\Tracing\PropagationContext; @@ -354,7 +355,7 @@ public function testDoesNotUsePropagationContextSpanIdAsParentSpanIdWhenNoLocalS $propagationContext->setSpanId(new SpanId('1234567890abcdef')); SentrySdk::init($client); - SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new IsolationScope($propagationContext)); $aggregator = new LogsAggregator(); $aggregator->add(LogLevel::info(), 'Test message'); diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 55c6a085e..e9f5765ed 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -34,7 +34,7 @@ public function testHandle($record, Breadcrumb $expectedBreadcrumb): void $handler->handle($record); $event = Event::createEvent(); - SentrySdk::getIsolationScope()->applyToEvent($event); + SentrySdk::getGlobalScope()->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $this->assertCount(1, $event->getBreadcrumbs()); diff --git a/tests/Monolog/ExceptionToSentryIssueHandlerTest.php b/tests/Monolog/ExceptionToSentryIssueHandlerTest.php index 839c2db54..74908c2fe 100644 --- a/tests/Monolog/ExceptionToSentryIssueHandlerTest.php +++ b/tests/Monolog/ExceptionToSentryIssueHandlerTest.php @@ -12,7 +12,7 @@ use Sentry\Event; use Sentry\Monolog\ExceptionToSentryIssueHandler; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\MergedScope; final class ExceptionToSentryIssueHandlerTest extends TestCase { @@ -30,7 +30,7 @@ public function testHandleCapturesExceptionAndAddsMetadata($record, \Throwable $ ->method('captureException') ->with( $this->identicalTo($exception), - $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { + $this->callback(function (MergedScope $scopeArg) use ($expectedExtra): bool { $event = $scopeArg->applyToEvent(Event::createEvent()); $this->assertNotNull($event); @@ -57,7 +57,7 @@ public function testHandleReturnsFalseWhenBubblingEnabled(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureException') - ->with($this->identicalTo($exception), $this->isInstanceOf(Scope::class), null); + ->with($this->identicalTo($exception), $this->isInstanceOf(MergedScope::class), null); SentrySdk::init($client); @@ -84,7 +84,7 @@ public function testHandleReturnsTrueWhenBubblingDisabled(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureException') - ->with($this->identicalTo($exception), $this->isInstanceOf(Scope::class), null); + ->with($this->identicalTo($exception), $this->isInstanceOf(MergedScope::class), null); SentrySdk::init($client); diff --git a/tests/Monolog/LogToSentryIssueHandlerTest.php b/tests/Monolog/LogToSentryIssueHandlerTest.php index ec9a53d60..664209121 100644 --- a/tests/Monolog/LogToSentryIssueHandlerTest.php +++ b/tests/Monolog/LogToSentryIssueHandlerTest.php @@ -16,7 +16,7 @@ use Sentry\Monolog\LogToSentryIssueHandler; use Sentry\SentrySdk; use Sentry\Severity; -use Sentry\State\Scope; +use Sentry\State\MergedScope; use Sentry\Tests\StubTransport; final class LogToSentryIssueHandlerTest extends TestCase @@ -49,7 +49,7 @@ public function testHandleCapturesLogMessageAsIssue(bool $fillExtraContext, $rec return true; }), - $this->callback(function (Scope $scopeArg) use ($expectedExtra): bool { + $this->callback(function (MergedScope $scopeArg) use ($expectedExtra): bool { $event = $scopeArg->applyToEvent(Event::createEvent()); $this->assertNotNull($event); @@ -73,7 +73,7 @@ public function testHandleReturnsTrueWhenBubblingDisabled(): void $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) ->method('captureEvent') - ->with($this->isInstanceOf(Event::class), $this->isInstanceOf(EventHint::class), $this->isInstanceOf(Scope::class)); + ->with($this->isInstanceOf(Event::class), $this->isInstanceOf(EventHint::class), $this->isInstanceOf(MergedScope::class)); SentrySdk::init($client); @@ -117,7 +117,7 @@ public function testHandleCapturesRecordsWithNonThrowableExceptionContext(): voi ->with( $this->isInstanceOf(Event::class), $this->isInstanceOf(EventHint::class), - $this->callback(function (Scope $scopeArg): bool { + $this->callback(function (MergedScope $scopeArg): bool { $event = $scopeArg->applyToEvent(Event::createEvent()); $this->assertNotNull($event); diff --git a/tests/SentrySdkTest.php b/tests/SentrySdkTest.php index d72be230f..911b8ee87 100644 --- a/tests/SentrySdkTest.php +++ b/tests/SentrySdkTest.php @@ -11,6 +11,7 @@ use Sentry\NoOpClient; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\State\IsolationScope; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\TransactionContext; @@ -32,7 +33,7 @@ public function testInitResetsRuntimeContext(): void $this->assertNotSame($previousScope, $currentScope); - $event = $currentScope->applyToEvent(Event::createEvent()); + $event = SentrySdk::getGlobalScope()->merge($currentScope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([], $event->getTags()); @@ -113,7 +114,7 @@ public function testInitDoesNotResetGlobalScope(): void $this->assertSame($globalScope, SentrySdk::getGlobalScope()); - $event = $globalScope->applyToEvent(Event::createEvent()); + $event = $globalScope->merge(new IsolationScope())->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['baseline' => 'yes'], $event->getTags()); @@ -132,7 +133,7 @@ public function testStartAndEndContextIsolateScopeData(): void SentrySdk::endContext(); $event = Event::createEvent(); - $event = SentrySdk::getIsolationScope()->applyToEvent($event); + $event = SentrySdk::getGlobalScope()->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $this->assertArrayHasKey('baseline', $event->getTags()); $this->assertArrayNotHasKey('request', $event->getTags()); @@ -286,7 +287,7 @@ public function testNestedWithContextReusesOuterContext(): void $event = Event::createEvent(); - $event = SentrySdk::getIsolationScope()->applyToEvent($event); + $event = SentrySdk::getGlobalScope()->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $this->assertNotSame($globalScope, SentrySdk::getIsolationScope()); $this->assertSame('yes', $event->getTags()['outer'] ?? null); diff --git a/tests/State/BreadcrumbRecorderTest.php b/tests/State/BreadcrumbRecorderTest.php index 121825aae..a61ef479f 100644 --- a/tests/State/BreadcrumbRecorderTest.php +++ b/tests/State/BreadcrumbRecorderTest.php @@ -11,15 +11,16 @@ use Sentry\NoOpClient; use Sentry\Options; use Sentry\State\BreadcrumbRecorder; -use Sentry\State\Scope; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; final class BreadcrumbRecorderTest extends TestCase { public function testRecordAddsBreadcrumbToGivenScope(): void { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $scope = new Scope(); - $otherScope = new Scope(); + $scope = new IsolationScope(); + $otherScope = new IsolationScope(); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -33,7 +34,7 @@ public function testRecordAddsBreadcrumbToGivenScope(): void public function testRecordReturnsFalseForNoOpClient(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $this->assertFalse(BreadcrumbRecorder::record( new NoOpClient(), @@ -45,7 +46,7 @@ public function testRecordReturnsFalseForNoOpClient(): void public function testRecordReturnsFalseWhenMaxBreadcrumbsLimitIsZero(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -62,7 +63,7 @@ public function testRecordReturnsFalseWhenMaxBreadcrumbsLimitIsZero(): void public function testRecordReturnsFalseWhenBeforeBreadcrumbCallbackReturnsNull(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -85,7 +86,7 @@ public function testRecordStoresBreadcrumbReturnedByBeforeBreadcrumbCallback(): { $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_WARNING, Breadcrumb::TYPE_DEFAULT, 'custom'); - $scope = new Scope(); + $scope = new IsolationScope(); $client = $this->createMock(ClientInterface::class); $client->expects($this->once()) @@ -105,7 +106,7 @@ public function testRecordRespectsMaxBreadcrumbsLimit(): void $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'one'); $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'two'); $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'three'); - $scope = new Scope(); + $scope = new IsolationScope(); $client = $this->createMock(ClientInterface::class); $client->expects($this->exactly(3)) @@ -122,9 +123,9 @@ public function testRecordRespectsMaxBreadcrumbsLimit(): void /** * @param Breadcrumb[] $expectedBreadcrumbs */ - private function assertScopeBreadcrumbs(Scope $scope, array $expectedBreadcrumbs): void + private function assertScopeBreadcrumbs(IsolationScope $scope, array $expectedBreadcrumbs): void { - $event = $scope->applyToEvent(Event::createEvent()); + $event = (new GlobalScope())->merge($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame($expectedBreadcrumbs, $event->getBreadcrumbs()); diff --git a/tests/State/EventCapturerTest.php b/tests/State/EventCapturerTest.php index 8e9123cda..d575bee86 100644 --- a/tests/State/EventCapturerTest.php +++ b/tests/State/EventCapturerTest.php @@ -17,7 +17,8 @@ use Sentry\SentrySdk; use Sentry\Severity; use Sentry\State\EventCapturer; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; +use Sentry\State\MergedScope; use Sentry\Util\SentryUid; final class EventCapturerTest extends TestCase @@ -31,7 +32,7 @@ public function testCaptureMessagePassesMergedScopeAndStoresLastEventIdOnIsolati $client->expects($this->once()) ->method('captureMessage') - ->with('foo', Severity::debug(), $this->callback(function (Scope $scope) use ($isolationScope): bool { + ->with('foo', Severity::debug(), $this->callback(function (MergedScope $scope) use ($isolationScope): bool { return $this->isMergedCaptureScope($scope, $isolationScope); }), $hint) ->willReturn($eventId); @@ -50,7 +51,7 @@ public function testCaptureExceptionPassesMergedScopeAndStoresLastEventIdOnIsola $client->expects($this->once()) ->method('captureException') - ->with($exception, $this->callback(function (Scope $scope) use ($isolationScope): bool { + ->with($exception, $this->callback(function (MergedScope $scope) use ($isolationScope): bool { return $this->isMergedCaptureScope($scope, $isolationScope); }), $hint) ->willReturn($eventId); @@ -68,7 +69,7 @@ public function testCaptureEventPassesMergedScopeAndStoresLastEventIdOnIsolation $client->expects($this->once()) ->method('captureEvent') - ->with($event, $hint, $this->callback(function (Scope $scope) use ($isolationScope): bool { + ->with($event, $hint, $this->callback(function (MergedScope $scope) use ($isolationScope): bool { return $this->isMergedCaptureScope($scope, $isolationScope); })) ->willReturn($event->getId()); @@ -86,7 +87,7 @@ public function testCaptureLastErrorPassesMergedScopeAndStoresLastEventIdOnIsola $client->expects($this->once()) ->method('captureLastError') - ->with($this->callback(function (Scope $scope) use ($isolationScope): bool { + ->with($this->callback(function (MergedScope $scope) use ($isolationScope): bool { return $this->isMergedCaptureScope($scope, $isolationScope); }), $hint) ->willReturn($eventId); @@ -104,7 +105,7 @@ public function testCaptureEventClearsLastEventIdWhenClientReturnsNull(): void $client->expects($this->once()) ->method('captureEvent') - ->with($event, null, $this->callback(function (Scope $scope) use ($isolationScope): bool { + ->with($event, null, $this->callback(function (MergedScope $scope) use ($isolationScope): bool { return $this->isMergedCaptureScope($scope, $isolationScope); })) ->willReturn(null); @@ -145,7 +146,7 @@ public function testCaptureCheckInCreatesEventAndStoresLastEventId(): void && $checkIn->getEnvironment() === Event::DEFAULT_ENVIRONMENT && $checkIn->getDuration() === 10 && $checkIn->getMonitorConfig() === $monitorConfig; - }), null, $this->callback(function (Scope $scope) use ($isolationScope): bool { + }), null, $this->callback(function (MergedScope $scope) use ($isolationScope): bool { return $this->isMergedCaptureScope($scope, $isolationScope); })) ->willReturn($eventId); @@ -170,7 +171,7 @@ public function testCaptureCheckInReturnsNullForNoOpClient(): void $this->assertSame($eventId, SentrySdk::getIsolationScope()->getLastEventId()); } - private function setClientAndIsolationScope(ClientInterface $client): Scope + private function setClientAndIsolationScope(ClientInterface $client): IsolationScope { SentrySdk::init(); @@ -179,7 +180,7 @@ private function setClientAndIsolationScope(ClientInterface $client): Scope SentrySdk::getGlobalScope()->setTag('global', 'yes'); SentrySdk::getGlobalScope()->setClient($client); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setTag('scope', 'isolation'); $scope->setTag('isolation', 'yes'); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($scope); @@ -187,7 +188,7 @@ private function setClientAndIsolationScope(ClientInterface $client): Scope return $scope; } - private function isMergedCaptureScope(Scope $captureScope, Scope $isolationScope): bool + private function isMergedCaptureScope(MergedScope $captureScope, IsolationScope $isolationScope): bool { $this->assertNotSame($isolationScope, $captureScope); diff --git a/tests/State/ScopeTest.php b/tests/State/ScopeTest.php index be9362868..24b6103d8 100644 --- a/tests/State/ScopeTest.php +++ b/tests/State/ScopeTest.php @@ -14,6 +14,9 @@ use Sentry\NoOpClient; use Sentry\Options; use Sentry\Severity; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; +use Sentry\State\MergedScope; use Sentry\State\Scope; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; @@ -27,9 +30,14 @@ final class ScopeTest extends TestCase { + private function mergeScope(IsolationScope $scope, ?GlobalScope $globalScope = null): MergedScope + { + return ($globalScope ?? new GlobalScope())->merge($scope); + } + public function testGetAndSetClient(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $this->assertInstanceOf(NoOpClient::class, $scope->getClient()); @@ -43,7 +51,7 @@ public function testClonedScopeKeepsClientShared(): void { $client = $this->createMock(ClientInterface::class); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setClient($client); $clonedScope = clone $scope; @@ -53,7 +61,7 @@ public function testClonedScopeKeepsClientShared(): void public function testGetAndSetLastEventId(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $this->assertNull($scope->getLastEventId()); @@ -69,8 +77,8 @@ public function testGetAndSetLastEventId(): void public function testSetTag(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getTags()); @@ -78,7 +86,7 @@ public function testSetTag(): void $scope->setTag('foo', 'bar'); $scope->setTag('bar', 'baz'); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); @@ -86,17 +94,17 @@ public function testSetTag(): void public function testSetTags(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setTags(['foo' => 'bar']); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar'], $event->getTags()); $scope->setTags(['bar' => 'baz']); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); @@ -104,20 +112,20 @@ public function testSetTags(): void public function testRemoveTag(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $scope->setTag('foo', 'bar'); $scope->setTag('bar', 'baz'); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getTags()); $scope->removeTag('foo'); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['bar' => 'baz'], $event->getTags()); @@ -125,8 +133,8 @@ public function testRemoveTag(): void public function testSetFlag(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertArrayNotHasKey('flags', $event->getContexts()); @@ -134,7 +142,7 @@ public function testSetFlag(): void $scope->addFeatureFlag('foo', true); $scope->addFeatureFlag('bar', false); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertArrayHasKey('flags', $event->getContexts()); @@ -154,8 +162,8 @@ public function testSetFlag(): void public function testSetFlagLimit(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertArrayNotHasKey('flags', $event->getContexts()); @@ -171,7 +179,7 @@ public function testSetFlagLimit(): void ]; } - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertArrayHasKey('flags', $event->getContexts()); @@ -186,7 +194,7 @@ public function testSetFlagLimit(): void 'result' => true, ]; - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertArrayHasKey('flags', $event->getContexts()); @@ -197,7 +205,7 @@ public function testSetFlagPropagatesToSpan(): void { $span = new Span(); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setSpan($span); $scope->addFeatureFlag('feature', true); @@ -209,10 +217,10 @@ public function testSetAndRemoveContext(): void { $propgationContext = PropagationContext::fromDefaults(); - $scope = new Scope($propgationContext); + $scope = new IsolationScope($propgationContext); $scope->setContext('foo', ['foo' => 'bar']); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([ @@ -225,7 +233,7 @@ public function testSetAndRemoveContext(): void $scope->removeContext('foo'); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([ @@ -237,7 +245,7 @@ public function testSetAndRemoveContext(): void $scope->setContext('foo', []); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([ @@ -250,8 +258,8 @@ public function testSetAndRemoveContext(): void public function testSetExtra(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getExtra()); @@ -259,7 +267,7 @@ public function testSetExtra(): void $scope->setExtra('foo', 'bar'); $scope->setExtra('bar', 'baz'); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); @@ -267,17 +275,17 @@ public function testSetExtra(): void public function testSetExtras(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setExtras(['foo' => 'bar']); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar'], $event->getExtra()); $scope->setExtras(['bar' => 'baz']); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $event->getExtra()); @@ -285,8 +293,8 @@ public function testSetExtras(): void public function testSetUser(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getUser()); @@ -296,21 +304,21 @@ public function testSetUser(): void $scope->setUser($user); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); - $this->assertSame($user, $event->getUser()); + $this->assertEquals($user, $event->getUser()); - $user = UserDataBag::createFromUserIpAddress('127.0.0.1'); - $user->setMetadata('subscription', 'basic'); - $user->setMetadata('subscription_expires_at', '2020-08-26'); + $expectedUser = UserDataBag::createFromUserIdentifier('unique_id'); + $expectedUser->setMetadata('subscription', 'basic'); + $expectedUser->setMetadata('subscription_expires_at', '2020-08-26'); $scope->setUser(['ip_address' => '127.0.0.1', 'subscription_expires_at' => '2020-08-26']); - $event = $scope->applyToEvent($event); + $event = $this->mergeScope($scope)->applyToEvent($event); $this->assertNotNull($event); - $this->assertEquals($user, $event->getUser()); + $this->assertEquals($expectedUser, $event->getUser()); } public function testSetUserThrowsOnInvalidArgument(): void @@ -318,23 +326,23 @@ public function testSetUserThrowsOnInvalidArgument(): void $this->expectException(\TypeError::class); $this->expectExceptionMessage('The $user argument must be either an array or an instance of the "Sentry\UserDataBag" class. Got: "string".'); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setUser('foo'); } public function testRemoveUser(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNotNull($event->getUser()); $scope->removeUser(); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getUser()); @@ -342,15 +350,15 @@ public function testRemoveUser(): void public function testSetFingerprint(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getFingerprint()); $scope->setFingerprint(['foo', 'bar']); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['foo', 'bar'], $event->getFingerprint()); @@ -358,15 +366,15 @@ public function testSetFingerprint(): void public function testSetLevel(): void { - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent()); + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getLevel()); $scope->setLevel(Severity::debug()); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEquals(Severity::debug(), $event->getLevel()); @@ -374,12 +382,12 @@ public function testSetLevel(): void public function testAddBreadcrumb(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $breadcrumb1 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb2 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $breadcrumb3 = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -387,14 +395,14 @@ public function testAddBreadcrumb(): void $scope->addBreadcrumb($breadcrumb1); $scope->addBreadcrumb($breadcrumb2); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb1, $breadcrumb2], $event->getBreadcrumbs()); $scope->addBreadcrumb($breadcrumb3, 2); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame([$breadcrumb2, $breadcrumb3], $event->getBreadcrumbs()); @@ -402,19 +410,19 @@ public function testAddBreadcrumb(): void public function testClearBreadcrumbs(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); $scope->addBreadcrumb(new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting')); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNotEmpty($event->getBreadcrumbs()); $scope->clearBreadcrumbs(); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertEmpty($event->getBreadcrumbs()); @@ -427,7 +435,7 @@ public function testAddEventProcessor(): void $callback3Called = false; $event = Event::createEvent(); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->addEventProcessor(function (Event $eventArg) use (&$callback1Called, $callback2Called, $callback3Called): ?Event { $this->assertFalse($callback2Called); @@ -438,7 +446,7 @@ public function testAddEventProcessor(): void return $eventArg; }); - $this->assertSame($event, $scope->applyToEvent($event)); + $this->assertSame($event, $this->mergeScope($scope)->applyToEvent($event)); $this->assertTrue($callback1Called); $scope->addEventProcessor(function () use ($callback1Called, &$callback2Called, $callback3Called) { @@ -456,7 +464,7 @@ public function testAddEventProcessor(): void return null; }); - $this->assertNull($scope->applyToEvent($event)); + $this->assertNull($this->mergeScope($scope)->applyToEvent($event)); $this->assertTrue($callback2Called); $this->assertFalse($callback3Called); } @@ -464,7 +472,7 @@ public function testAddEventProcessor(): void public function testEventProcessorReceivesTheEventAndEventHint(): void { $event = Event::createEvent(); - $scope = new Scope(); + $scope = new IsolationScope(); $hint = new EventHint(); $processorCalled = false; @@ -477,14 +485,14 @@ public function testEventProcessorReceivesTheEventAndEventHint(): void return $eventArg; }); - $this->assertSame($event, $scope->applyToEvent($event, $hint)); + $this->assertSame($event, $this->mergeScope($scope)->applyToEvent($event, $hint)); $this->assertSame($hint, $processorReceivedHint); $this->assertTrue($processorCalled); } public function testClear(): void { - $scope = new Scope(); + $scope = new IsolationScope(); $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_ERROR, Breadcrumb::TYPE_ERROR, 'error_reporting'); $client = $this->createMock(ClientInterface::class); $eventId = EventId::generate(); @@ -500,7 +508,7 @@ public function testClear(): void $scope->setUser(UserDataBag::createFromUserIdentifier('unique_id')); $scope->clear(); - $event = $scope->applyToEvent(Event::createEvent()); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertNull($event->getLevel()); @@ -530,7 +538,7 @@ public function testApplyToEvent(): void $span = $transaction->startChild(new SpanContext()); $span->setSpanId(new SpanId('566e3688a61d4bc8')); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setLevel(Severity::warning()); $scope->setFingerprint(['foo']); $scope->addBreadcrumb($breadcrumb); @@ -542,13 +550,13 @@ public function testApplyToEvent(): void $scope->addFeatureFlag('feature', true); $scope->setSpan($span); - $this->assertSame($event, $scope->applyToEvent($event)); + $this->assertSame($event, $this->mergeScope($scope)->applyToEvent($event)); $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); $this->assertSame(['foo'], $event->getFingerprint()); $this->assertSame([$breadcrumb], $event->getBreadcrumbs()); $this->assertSame(['foo' => 'bar'], $event->getTags()); $this->assertSame(['bar' => 'foo'], $event->getExtra()); - $this->assertSame($user, $event->getUser()); + $this->assertEquals($user, $event->getUser()); $this->assertSame([ 'foocontext' => [ 'foo' => 'foo', @@ -591,7 +599,7 @@ public function testMergeScopesAppliesGlobalScopeUnderIsolationScope(): void $globalUser->setMetadata('shared', 'global'); $globalUser->setMetadata('global', true); - $globalScope = new Scope(); + $globalScope = new GlobalScope(); $globalScope->setTag('shared', 'global'); $globalScope->setTag('global', 'tag'); $globalScope->setExtra('shared', 'global'); @@ -602,15 +610,13 @@ public function testMergeScopesAppliesGlobalScopeUnderIsolationScope(): void $globalScope->setLevel(Severity::error()); $globalScope->setFingerprint(['global-fingerprint']); $globalScope->addBreadcrumb($globalBreadcrumb); - $globalScope->addFeatureFlag('shared-flag', false); - $globalScope->addFeatureFlag('global-flag', true); $globalScope->addAttachment($globalAttachment); $isolationUser = UserDataBag::createFromUserIdentifier('isolation-user'); $isolationUser->setMetadata('shared', 'isolation'); $isolationUser->setMetadata('isolation', true); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); $isolationScope->setTag('shared', 'isolation'); $isolationScope->setTag('isolation', 'tag'); $isolationScope->setExtra('shared', 'isolation'); @@ -637,7 +643,7 @@ public function testMergeScopesAppliesGlobalScopeUnderIsolationScope(): void $event->setUser($eventUser); $event->setFingerprint(['event-fingerprint']); - $event = Scope::mergeScopes($globalScope, $isolationScope)->applyToEvent($event); + $event = $globalScope->merge($isolationScope)->applyToEvent($event); $this->assertNotNull($event); $this->assertTrue($event->getLevel()->isEqualTo(Severity::warning())); @@ -659,10 +665,6 @@ public function testMergeScopesAppliesGlobalScopeUnderIsolationScope(): void $this->assertSame(['value' => 'isolation'], $event->getContexts()['isolation_context']); $this->assertSame([ 'values' => [ - [ - 'flag' => 'global-flag', - 'result' => true, - ], [ 'flag' => 'shared-flag', 'result' => true, @@ -689,10 +691,10 @@ public function testMergeScopesAppliesGlobalScopeUnderIsolationScope(): void public function testMergeScopesUsesGlobalLevelWhenIsolationLevelIsUnset(): void { - $globalScope = new Scope(); + $globalScope = new GlobalScope(); $globalScope->setLevel(Severity::error()); - $event = Scope::mergeScopes($globalScope, new Scope())->applyToEvent(Event::createEvent()); + $event = $globalScope->merge(new IsolationScope())->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertTrue($event->getLevel()->isEqualTo(Severity::error())); @@ -700,35 +702,39 @@ public function testMergeScopesUsesGlobalLevelWhenIsolationLevelIsUnset(): void public function testMergeScopesCarriesIsolationClient(): void { - $globalScope = new Scope(); + $globalScope = new GlobalScope(); $globalScope->setClient($this->createMock(ClientInterface::class)); $isolationClient = $this->createMock(ClientInterface::class); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); $isolationScope->setClient($isolationClient); - $this->assertSame($isolationClient, Scope::mergeScopes($globalScope, $isolationScope)->getClient()); + $this->assertSame($isolationClient, $globalScope->merge($isolationScope)->getClient()); } public function testMergeScopesCapsBreadcrumbsAndFlags(): void { - $globalScope = new Scope(); + $globalScope = new GlobalScope(); $globalBreadcrumbs = []; foreach (range(1, 100) as $i) { $breadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, "global{$i}"); $globalBreadcrumbs[] = $breadcrumb; $globalScope->addBreadcrumb($breadcrumb); - $globalScope->addFeatureFlag("feature{$i}", true); } $isolationBreadcrumb = new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'isolation'); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); $isolationScope->addBreadcrumb($isolationBreadcrumb); + + foreach (range(1, Scope::MAX_FLAGS) as $i) { + $isolationScope->addFeatureFlag("feature{$i}", true); + } + $isolationScope->addFeatureFlag('feature50', false); $isolationScope->addFeatureFlag('feature101', true); - $event = Scope::mergeScopes($globalScope, $isolationScope)->applyToEvent(Event::createEvent()); + $event = $globalScope->merge($isolationScope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertCount(100, $event->getBreadcrumbs()); @@ -754,24 +760,15 @@ public function testMergeScopesCapsBreadcrumbsAndFlags(): void public function testMergeScopesKeepsTraceStateFromIsolationScope(): void { - $globalPropagationContext = PropagationContext::fromDefaults(); - $globalPropagationContext->setTraceId(new TraceId('11111111111111111111111111111111')); - $globalPropagationContext->setSpanId(new SpanId('1111111111111111')); - - $globalSpan = new Span(); - $globalSpan->setTraceId(new TraceId('22222222222222222222222222222222')); - $globalSpan->setSpanId(new SpanId('2222222222222222')); - - $globalScope = new Scope($globalPropagationContext); - $globalScope->setSpan($globalSpan); + $globalScope = new GlobalScope(); $isolationPropagationContext = PropagationContext::fromDefaults(); $isolationPropagationContext->setTraceId(new TraceId('33333333333333333333333333333333')); $isolationPropagationContext->setSpanId(new SpanId('3333333333333333')); - $isolationScope = new Scope($isolationPropagationContext); + $isolationScope = new IsolationScope($isolationPropagationContext); - $mergedScope = Scope::mergeScopes($globalScope, $isolationScope); + $mergedScope = $globalScope->merge($isolationScope); $this->assertNull($mergedScope->getSpan()); $this->assertNotSame($isolationScope->getPropagationContext(), $mergedScope->getPropagationContext()); @@ -799,21 +796,21 @@ public function testMergeScopesKeepsProcessorOrder(): void return $event; }); - $globalScope = new Scope(); + $globalScope = new GlobalScope(); $globalScope->addEventProcessor(static function (Event $event) use (&$calls): ?Event { $calls[] = 'global'; return $event; }); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); $isolationScope->addEventProcessor(static function (Event $event) use (&$calls): ?Event { $calls[] = 'isolation'; return $event; }); - $event = Scope::mergeScopes($globalScope, $isolationScope)->applyToEvent(Event::createEvent()); + $event = $globalScope->merge($isolationScope)->applyToEvent(Event::createEvent()); $this->assertNotNull($event); $this->assertSame(['static', 'global', 'isolation'], $calls); @@ -824,9 +821,9 @@ public function testMergeScopesKeepsProcessorOrder(): void */ public function testAttachmentsAppliedForType(Event $event, int $attachmentCount): void { - $scope = new Scope(); + $scope = new IsolationScope(); $scope->addAttachment(Attachment::fromBytes('test', 'abcde')); - $scope->applyToEvent($event); + $this->mergeScope($scope)->applyToEvent($event); $this->assertCount($attachmentCount, $event->getAttachments()); } @@ -851,7 +848,7 @@ public function testGetTraceContextPrefersExternalPropagationContextOverPropagat ]; }); - $scope = new Scope($propagationContext); + $scope = new IsolationScope($propagationContext); $this->assertSame([ 'trace_id' => '771a43a4192642f0b136d5159a501700', @@ -876,7 +873,7 @@ public function testGetTraceContextPrefersLocalSpanOverExternalPropagationContex $span = $transaction->startChild(new SpanContext()); $span->setSpanId(new SpanId('566e3688a61d4bc8')); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setSpan($span); $this->assertSame([ @@ -898,8 +895,8 @@ public function testApplyToEventSkipsDynamicSamplingContextWhenUsingExternalProp ]; }); - $scope = new Scope(); - $event = $scope->applyToEvent(Event::createEvent(), null, new Options([ + $scope = new IsolationScope(); + $event = $this->mergeScope($scope)->applyToEvent(Event::createEvent(), null, new Options([ 'dsn' => 'http://public@example.com/1', 'release' => '1.0.0', 'environment' => 'test', diff --git a/tests/Tracing/DynamicSamplingContextTest.php b/tests/Tracing/DynamicSamplingContextTest.php index 7f1c153c4..70424e0d0 100644 --- a/tests/Tracing/DynamicSamplingContextTest.php +++ b/tests/Tracing/DynamicSamplingContextTest.php @@ -8,7 +8,7 @@ use Sentry\ClientInterface; use Sentry\NoOpClient; use Sentry\Options; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\TraceId; @@ -135,7 +135,7 @@ public function testFromOptions(): void $propagationContext->setTraceId(new TraceId('21160e9b836d479f81611368b2aa3d2c')); $propagationContext->setSampleRand(0.5); - $scope = new Scope(); + $scope = new IsolationScope(); $scope->setPropagationContext($propagationContext); $samplingContext = DynamicSamplingContext::fromOptions($options, $scope); @@ -156,7 +156,7 @@ public function testFromOptionsUsesConfiguredOrgIdOverDsnOrgId(): void 'org_id' => 2, ]); - $scope = new Scope(); + $scope = new IsolationScope(); $samplingContext = DynamicSamplingContext::fromOptions($options, $scope); $this->assertSame('2', $samplingContext->get('org_id')); @@ -168,7 +168,7 @@ public function testFromOptionsFallsBackToDsnOrgId(): void 'dsn' => 'http://public@o1.example.com/1', ]); - $scope = new Scope(); + $scope = new IsolationScope(); $samplingContext = DynamicSamplingContext::fromOptions($options, $scope); $this->assertSame('1', $samplingContext->get('org_id')); diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index e0f3df44e..61610f5ca 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -16,6 +16,8 @@ use Sentry\EventType; use Sentry\Options; use Sentry\SentrySdk; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; use Sentry\State\Scope; use Sentry\Tracing\GuzzleTracingMiddleware; use Sentry\Tracing\SpanStatus; @@ -60,7 +62,7 @@ public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void $event = Event::createEvent(); - SentrySdk::getIsolationScope()->applyToEvent($event); + (new GlobalScope())->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $this->assertCount(1, $event->getBreadcrumbs()); } @@ -103,7 +105,7 @@ public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void $event = Event::createEvent(); - SentrySdk::getIsolationScope()->applyToEvent($event); + (new GlobalScope())->merge(SentrySdk::getIsolationScope())->applyToEvent($event); $this->assertCount(1, $event->getBreadcrumbs()); } @@ -123,7 +125,7 @@ public function testTraceUsesProvidedIsolationScope(): void $currentScope = SentrySdk::getIsolationScope(); - $providedScope = new Scope(); + $providedScope = new IsolationScope(); $providedScope->setClient($client); $request = new Request('GET', 'https://www.example.com'); @@ -149,10 +151,10 @@ public function testTraceUsesProvidedIsolationScope(): void $this->assertSame($expectedPromiseResult, $promiseResult); $currentEvent = Event::createEvent(); - $currentScope->applyToEvent($currentEvent); + (new GlobalScope())->merge($currentScope)->applyToEvent($currentEvent); $providedEvent = Event::createEvent(); - $providedScope->applyToEvent($providedEvent); + (new GlobalScope())->merge($providedScope)->applyToEvent($providedEvent); $this->assertCount(0, $currentEvent->getBreadcrumbs()); $this->assertCount(1, $providedEvent->getBreadcrumbs()); @@ -377,7 +379,7 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec ->with($this->callback(function (Event $eventArg) use ($scope, $request, $expectedPromiseResult, $expectedBreadcrumbData, $expectedSpanData): bool { $this->assertSame(EventType::transaction(), $eventArg->getType()); - $scope->applyToEvent($eventArg); + (new GlobalScope())->merge($scope)->applyToEvent($eventArg); $spans = $eventArg->getSpans(); $breadcrumbs = $eventArg->getBreadcrumbs(); diff --git a/tests/Tracing/PropagationContextTest.php b/tests/Tracing/PropagationContextTest.php index 76484896f..8d2421913 100644 --- a/tests/Tracing/PropagationContextTest.php +++ b/tests/Tracing/PropagationContextTest.php @@ -8,7 +8,7 @@ use Sentry\ClientInterface; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\IsolationScope; use Sentry\Tracing\DynamicSamplingContext; use Sentry\Tracing\PropagationContext; use Sentry\Tracing\SpanId; @@ -130,7 +130,7 @@ public function testToBaggageUsesCurrentClientAndIsolationScope(): void ])); SentrySdk::getGlobalScope()->setClient($client); - SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new Scope($propagationContext)); + SentrySdk::getCurrentRuntimeContext()->setIsolationScope(new IsolationScope($propagationContext)); $this->assertSame( 'sentry-trace_id=566e3688a61d4bc888951642d6f14a19,sentry-sample_rand=0.25,sentry-release=1.0.0,sentry-environment=development', @@ -224,7 +224,7 @@ public function testGettersAndSetters(string $getterMethod, string $setterMethod public static function gettersAndSettersDataProvider(): array { - $scope = new Scope(); + $scope = new IsolationScope(); $options = new Options([ 'dsn' => 'http://public@example.com/sentry/1', 'release' => '1.0.0', diff --git a/tests/Tracing/SpanTest.php b/tests/Tracing/SpanTest.php index 1de0abf6d..251a9eaf8 100644 --- a/tests/Tracing/SpanTest.php +++ b/tests/Tracing/SpanTest.php @@ -7,7 +7,8 @@ use PHPUnit\Framework\TestCase; use Sentry\Event; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\GlobalScope; +use Sentry\State\IsolationScope; use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; @@ -83,17 +84,17 @@ public function testSetHttpStatusWritesResponseContextToCurrentIsolationScopeOnl 'status_code' => 201, ]); - $isolationScope = new Scope(); + $isolationScope = new IsolationScope(); SentrySdk::getCurrentRuntimeContext()->setIsolationScope($isolationScope); $span = new Span(); $span->setHttpStatus(404); - $isolationEvent = $isolationScope->applyToEvent(Event::createEvent()); + $isolationEvent = (new GlobalScope())->merge($isolationScope)->applyToEvent(Event::createEvent()); $this->assertNotNull($isolationEvent); $this->assertSame(404, $isolationEvent->getContexts()['response']['status_code']); - $globalEvent = $globalScope->applyToEvent(Event::createEvent()); + $globalEvent = $globalScope->merge(new IsolationScope())->applyToEvent(Event::createEvent()); $this->assertNotNull($globalEvent); $this->assertSame(201, $globalEvent->getContexts()['response']['status_code']); } diff --git a/tests/Tracing/TransactionTest.php b/tests/Tracing/TransactionTest.php index bf77563f2..cd3946ccc 100644 --- a/tests/Tracing/TransactionTest.php +++ b/tests/Tracing/TransactionTest.php @@ -11,7 +11,7 @@ use Sentry\EventType; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\State\Scope; +use Sentry\State\MergedScope; use Sentry\Tests\TestUtil\ClockMock; use Sentry\Tracing\SpanContext; use Sentry\Tracing\Transaction; @@ -59,7 +59,7 @@ public function testFinish(): void $this->assertSame([$span1, $span2], $eventArg->getSpans()); return true; - }), null, $this->isInstanceOf(Scope::class)) + }), null, $this->isInstanceOf(MergedScope::class)) ->willReturnCallback(static function (Event $eventArg) use (&$expectedEventId): EventId { $expectedEventId = $eventArg->getId();