From 54f6ed1f1fdb7bb399b97b57f078787dabcb6a8c Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Tue, 23 Jun 2026 23:49:35 +0200 Subject: [PATCH] feat(scopes): remove hub usage in handler and middleware --- src/ClientReport/ClientReportAggregator.php | 22 ++-- .../AbstractErrorListenerIntegration.php | 15 ++- src/Integration/ErrorListenerIntegration.php | 10 +- .../ExceptionListenerIntegration.php | 8 +- .../FatalErrorListenerIntegration.php | 10 +- src/Monolog/BreadcrumbHandler.php | 24 ++-- src/Monolog/ExceptionToSentryIssueHandler.php | 17 +-- src/Monolog/LogToSentryIssueHandler.php | 16 +-- src/Tracing/GuzzleTracingMiddleware.php | 30 ++--- src/functions.php | 12 +- .../ClientReportAggregatorTest.php | 18 +-- tests/Monolog/BreadcrumbHandlerTest.php | 42 ++++--- .../ExceptionToSentryIssueHandlerTest.php | 24 ++-- tests/Monolog/LogToSentryIssueHandlerTest.php | 30 +++-- tests/Tracing/GuzzleTracingMiddlewareTest.php | 114 ++++++++++++------ ...errors_not_silencable_on_php_8_and_up.phpt | 2 +- ...spects_capture_silenced_errors_option.phpt | 2 +- ...espects_current_error_reporting_level.phpt | 2 +- ..._option_regardless_of_error_reporting.phpt | 2 +- ...tegration_respects_error_types_option.phpt | 2 +- .../error_handler_captures_fatal_error.phpt | 2 +- ...rror_integration_captures_fatal_error.phpt | 2 +- ...tegration_respects_error_types_option.phpt | 2 +- .../error_handler_captures_fatal_error.phpt | 2 +- ...rror_integration_captures_fatal_error.phpt | 2 +- ...tegration_respects_error_types_option.phpt | 2 +- 26 files changed, 229 insertions(+), 185 deletions(-) diff --git a/src/ClientReport/ClientReportAggregator.php b/src/ClientReport/ClientReportAggregator.php index 8045a51b2b..454b0ba267 100644 --- a/src/ClientReport/ClientReportAggregator.php +++ b/src/ClientReport/ClientReportAggregator.php @@ -5,7 +5,7 @@ namespace Sentry\ClientReport; use Sentry\Event; -use Sentry\State\HubAdapter; +use Sentry\SentrySdk; use Sentry\Transport\DataCategory; class ClientReportAggregator @@ -35,15 +35,13 @@ public function add(DataCategory $category, Reason $reason, int $quantity): void $category = $category->getValue(); $reason = $reason->getValue(); if ($quantity <= 0) { - $client = HubAdapter::getInstance()->getClient(); - if ($client !== null) { - $logger = $client->getOptions()->getLoggerOrNullLogger(); - $logger->debug('Dropping Client report with category={category} and reason={reason} because quantity is zero or negative ({quantity})', [ - 'category' => $category, - 'reason' => $reason, - 'quantity' => $quantity, - ]); - } + $client = SentrySdk::getClient(); + $logger = $client->getOptions()->getLoggerOrNullLogger(); + $logger->debug('Dropping Client report with category={category} and reason={reason} because quantity is zero or negative ({quantity})', [ + 'category' => $category, + 'reason' => $reason, + 'quantity' => $quantity, + ]); return; } @@ -64,11 +62,11 @@ public function flush(): void $event = Event::createClientReport(); $event->setClientReports($reports); - $client = HubAdapter::getInstance()->getClient(); + $client = SentrySdk::getClient(); // Reset the client reports only if we successfully sent an event. If it fails it // can be sent on the next flush, or it gets discarded anyway. - if ($client !== null && $client->captureEvent($event) !== null) { + if ($client->captureEvent($event) !== null) { $this->reports = []; } } diff --git a/src/Integration/AbstractErrorListenerIntegration.php b/src/Integration/AbstractErrorListenerIntegration.php index a8894a7e29..97123b1138 100644 --- a/src/Integration/AbstractErrorListenerIntegration.php +++ b/src/Integration/AbstractErrorListenerIntegration.php @@ -6,23 +6,22 @@ use Sentry\Event; use Sentry\ExceptionMechanism; -use Sentry\State\HubInterface; +use Sentry\State\EventCapturer; use Sentry\State\Scope; +use function Sentry\withIsolationScope; + abstract class AbstractErrorListenerIntegration implements IntegrationInterface { /** - * Captures the exception using the given hub instance. - * - * @param HubInterface $hub The hub instance - * @param \Throwable $exception The exception instance + * @param \Throwable $exception The exception instance */ - protected function captureException(HubInterface $hub, \Throwable $exception): void + protected function captureException(\Throwable $exception): void { - $hub->withScope(function (Scope $scope) use ($hub, $exception): void { + withIsolationScope(function (Scope $scope) use ($exception): void { $scope->addEventProcessor(\Closure::fromCallable([$this, 'addExceptionMechanismToEvent'])); - $hub->captureException($exception); + EventCapturer::captureException($exception); }); } diff --git a/src/Integration/ErrorListenerIntegration.php b/src/Integration/ErrorListenerIntegration.php index a4b95c2ba7..fa33cd2a0c 100644 --- a/src/Integration/ErrorListenerIntegration.php +++ b/src/Integration/ErrorListenerIntegration.php @@ -33,24 +33,22 @@ public function setupOnce(): void ErrorHandler::registerOnceErrorHandler($this->options) ->addErrorHandlerListener( static function (\ErrorException $exception): void { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); + $client = SentrySdk::getClient(); + $integration = $client->getIntegration(self::class); if ($integration === null) { return; } - $client = $currentHub->getClient(); - if ($exception instanceof SilencedErrorException && !$client->getOptions()->shouldCaptureSilencedErrors()) { return; } - if (!$exception instanceof SilencedErrorException && !($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { + if (!$exception instanceof SilencedErrorException && ($client->getOptions()->getErrorTypes() & $exception->getSeverity()) === 0) { return; } - $integration->captureException($currentHub, $exception); + $integration->captureException($exception); } ); } diff --git a/src/Integration/ExceptionListenerIntegration.php b/src/Integration/ExceptionListenerIntegration.php index 18e7afc757..c86ff02bb2 100644 --- a/src/Integration/ExceptionListenerIntegration.php +++ b/src/Integration/ExceptionListenerIntegration.php @@ -20,16 +20,14 @@ public function setupOnce(): void { $errorHandler = ErrorHandler::registerOnceExceptionHandler(); $errorHandler->addExceptionHandlerListener(static function (\Throwable $exception): void { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); + $client = SentrySdk::getClient(); + $integration = $client->getIntegration(self::class); - // The client bound to the current hub, if any, could not have this - // integration enabled. If this is the case, bail out if ($integration === null) { return; } - $integration->captureException($currentHub, $exception); + $integration->captureException($exception); }); } } diff --git a/src/Integration/FatalErrorListenerIntegration.php b/src/Integration/FatalErrorListenerIntegration.php index 3cc0566688..e4b53c279c 100644 --- a/src/Integration/FatalErrorListenerIntegration.php +++ b/src/Integration/FatalErrorListenerIntegration.php @@ -22,20 +22,18 @@ public function setupOnce(): void { $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); $errorHandler->addFatalErrorHandlerListener(static function (FatalErrorException $exception): void { - $currentHub = SentrySdk::getCurrentHub(); - $integration = $currentHub->getIntegration(self::class); + $client = SentrySdk::getClient(); + $integration = $client->getIntegration(self::class); if ($integration === null) { return; } - $client = $currentHub->getClient(); - - if (!($client->getOptions()->getErrorTypes() & $exception->getSeverity())) { + if (($client->getOptions()->getErrorTypes() & $exception->getSeverity()) === 0) { return; } - $integration->captureException($currentHub, $exception); + $integration->captureException($exception); }); } } diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index 29e166671b..7197db2027 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -11,7 +11,8 @@ use Psr\Log\LogLevel; use Sentry\Breadcrumb; use Sentry\Event; -use Sentry\State\HubInterface; +use Sentry\SentrySdk; +use Sentry\State\BreadcrumbRecorder; use Sentry\State\Scope; /** @@ -21,23 +22,15 @@ final class BreadcrumbHandler extends AbstractProcessingHandler { /** - * @var HubInterface - */ - private $hub; - - /** - * @param HubInterface $hub The hub to which errors are reported - * @param int|string $level The minimum logging level at which this - * handler will be triggered - * @param bool $bubble Whether the messages that are handled can - * bubble up the stack or not + * @param int|string $level The minimum logging level at which this + * handler will be triggered + * @param bool $bubble Whether the messages that are handled can + * bubble up the stack or not * * @phpstan-param int|string|Level|LogLevel::* $level */ - public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { - $this->hub = $hub; - parent::__construct($level, $bubble); } @@ -66,7 +59,8 @@ protected function write($record): void $timestamp ); - $this->hub->addBreadcrumb($breadcrumb); + $scope = SentrySdk::getIsolationScope(); + BreadcrumbRecorder::record(SentrySdk::getClient($scope), $scope, $breadcrumb); } /** diff --git a/src/Monolog/ExceptionToSentryIssueHandler.php b/src/Monolog/ExceptionToSentryIssueHandler.php index b2e7abeb6b..4ab13e89d3 100644 --- a/src/Monolog/ExceptionToSentryIssueHandler.php +++ b/src/Monolog/ExceptionToSentryIssueHandler.php @@ -9,26 +9,21 @@ use Monolog\Logger; use Monolog\LogRecord; use Psr\Log\LogLevel; -use Sentry\State\HubInterface; +use Sentry\State\EventCapturer; use Sentry\State\Scope; +use function Sentry\withIsolationScope; + /** * This Monolog handler will collect monolog events and send them to sentry. */ class ExceptionToSentryIssueHandler extends AbstractHandler { - /** - * @var HubInterface - */ - private $hub; - /** * @phpstan-param value-of|value-of|Level|LogLevel::* $level */ - public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { - $this->hub = $hub; - parent::__construct($level, $bubble); } @@ -44,7 +39,7 @@ public function handle($record): bool return false; } - $this->hub->withScope(function (Scope $scope) use ($record, $exception): void { + withIsolationScope(function (Scope $scope) use ($record, $exception): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); $scope->setExtra('monolog.message', $record['message']); @@ -61,7 +56,7 @@ public function handle($record): bool $scope->setExtra('monolog.extra', $monologExtraData); } - $this->hub->captureException($exception); + EventCapturer::captureException($exception); }); return $this->bubble === false; diff --git a/src/Monolog/LogToSentryIssueHandler.php b/src/Monolog/LogToSentryIssueHandler.php index 18dd6eab61..e7c8d9b44d 100644 --- a/src/Monolog/LogToSentryIssueHandler.php +++ b/src/Monolog/LogToSentryIssueHandler.php @@ -11,9 +11,11 @@ use Psr\Log\LogLevel; use Sentry\Event; use Sentry\EventHint; -use Sentry\State\HubInterface; +use Sentry\State\EventCapturer; use Sentry\State\Scope; +use function Sentry\withIsolationScope; + /** * This Monolog handler captures log messages as Sentry issues. */ @@ -23,11 +25,6 @@ class LogToSentryIssueHandler extends AbstractProcessingHandler private const CONTEXT_EXCEPTION_KEY = 'exception'; - /** - * @var HubInterface - */ - private $hub; - /** * @var bool */ @@ -36,9 +33,8 @@ class LogToSentryIssueHandler extends AbstractProcessingHandler /** * @phpstan-param value-of|value-of|Level|LogLevel::* $level */ - public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bubble = true, bool $fillExtraContext = false) + public function __construct($level = Logger::DEBUG, bool $bubble = true, bool $fillExtraContext = false) { - $this->hub = $hub; $this->fillExtraContext = $fillExtraContext; parent::__construct($level, $bubble); @@ -70,7 +66,7 @@ protected function doWrite($record): void $hint = new EventHint(); - $this->hub->withScope(function (Scope $scope) use ($record, $event, $hint): void { + withIsolationScope(function (Scope $scope) use ($record, $event, $hint): void { $scope->setExtra('monolog.channel', $record['channel']); $scope->setExtra('monolog.level', $record['level_name']); @@ -88,7 +84,7 @@ protected function doWrite($record): void } } - $this->hub->captureEvent($event, $hint); + EventCapturer::captureEvent($event, $hint); }); } diff --git a/src/Tracing/GuzzleTracingMiddleware.php b/src/Tracing/GuzzleTracingMiddleware.php index 8c14fe0b07..27cdca32a2 100644 --- a/src/Tracing/GuzzleTracingMiddleware.php +++ b/src/Tracing/GuzzleTracingMiddleware.php @@ -11,7 +11,8 @@ use Sentry\Breadcrumb; use Sentry\ClientInterface; use Sentry\SentrySdk; -use Sentry\State\HubInterface; +use Sentry\State\BreadcrumbRecorder; +use Sentry\State\Scope; use function Sentry\getBaggage; use function Sentry\getTraceparent; @@ -21,13 +22,13 @@ */ final class GuzzleTracingMiddleware { - public static function trace(?HubInterface $hub = null): \Closure + public static function trace(?Scope $scope = null): \Closure { - return static function (callable $handler) use ($hub): \Closure { - return static function (RequestInterface $request, array $options) use ($hub, $handler) { - $hub = $hub ?? SentrySdk::getCurrentHub(); - $client = $hub->getClient(); - $parentSpan = $hub->getSpan(); + return static function (callable $handler) use ($scope): \Closure { + return static function (RequestInterface $request, array $options) use ($handler, $scope) { + $scope = $scope ?? SentrySdk::getIsolationScope(); + $client = SentrySdk::getClient($scope); + $parentSpan = $scope->getSpan(); $partialUri = Uri::fromParts([ 'scheme' => $request->getUri()->getScheme(), @@ -59,28 +60,27 @@ public static function trace(?HubInterface $hub = null): \Closure $childSpan = $parentSpan->startChild($spanContext); - $hub->setSpan($childSpan); + $scope->setSpan($childSpan); } if (self::shouldAttachTracingHeaders($client, $request)) { - $traceParent = getTraceparent(); + $traceParent = getTraceparent($scope); if ($traceParent !== '') { $request = $request->withHeader('sentry-trace', $traceParent); } - $baggage = getBaggage(); + $baggage = getBaggage($scope); if ($baggage !== '') { $request = $request->withHeader('baggage', $baggage); } } - $handlerPromiseCallback = static function ($responseOrException) use ($hub, $spanAndBreadcrumbData, $childSpan, $parentSpan, $partialUri) { + $handlerPromiseCallback = static function ($responseOrException) use ($client, $scope, $spanAndBreadcrumbData, $childSpan, $parentSpan, $partialUri) { if ($childSpan !== null) { - // We finish the span (which means setting the span end timestamp) first to ensure the measured time - // the span spans is as close to only the HTTP request time and do the data collection afterwards + // We finish the span first to keep the measured duration as close as possible to the HTTP request time. $childSpan->finish(); - $hub->setSpan($parentSpan); + $scope->setSpan($parentSpan); } $response = null; @@ -113,7 +113,7 @@ public static function trace(?HubInterface $hub = null): \Closure } } - $hub->addBreadcrumb(new Breadcrumb( + BreadcrumbRecorder::record($client, $scope, new Breadcrumb( $breadcrumbLevel, Breadcrumb::TYPE_HTTP, 'http', diff --git a/src/functions.php b/src/functions.php index eea47629fb..9db1674d4d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -381,11 +381,11 @@ 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(): string +function getTraceparent(?Scope $scope = null): string { - $client = SentrySdk::getClient(); + $scope = $scope ?? SentrySdk::getIsolationScope(); + $client = SentrySdk::getClient($scope); $options = $client->getOptions(); - $scope = SentrySdk::getIsolationScope(); if ($options->isTracingEnabled()) { $span = $scope->getSpan(); @@ -407,11 +407,11 @@ function getTraceparent(): 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(): string +function getBaggage(?Scope $scope = null): string { - $client = SentrySdk::getClient(); + $scope = $scope ?? SentrySdk::getIsolationScope(); + $client = SentrySdk::getClient($scope); $options = $client->getOptions(); - $scope = SentrySdk::getIsolationScope(); if ($options->isTracingEnabled()) { $span = $scope->getSpan(); diff --git a/tests/ClientReport/ClientReportAggregatorTest.php b/tests/ClientReport/ClientReportAggregatorTest.php index 7766c7ea50..9c117a3cbe 100644 --- a/tests/ClientReport/ClientReportAggregatorTest.php +++ b/tests/ClientReport/ClientReportAggregatorTest.php @@ -11,11 +11,12 @@ use Sentry\NoOpClient; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\State\Hub; use Sentry\Tests\StubLogger; use Sentry\Tests\StubTransport; use Sentry\Transport\DataCategory; +use function Sentry\captureMessage; + class ClientReportAggregatorTest extends TestCase { protected function setUp(): void @@ -23,7 +24,7 @@ protected function setUp(): void ini_set('zend.exception_ignore_args', '0'); StubTransport::$events = []; StubLogger::$logs = []; - SentrySdk::init()->bindClient(new Client(new Options([ + SentrySdk::init(new Client(new Options([ 'logger' => StubLogger::getInstance(), ]), StubTransport::getInstance())); } @@ -69,16 +70,15 @@ public function testClientReportAggregation(): void public function testFlushDoesNotOverwriteLastEventId(): void { - $hub = SentrySdk::getCurrentHub(); - $eventId = $hub->captureMessage('foo'); + $eventId = captureMessage('foo'); $this->assertNotNull($eventId); - $this->assertSame($eventId, $hub->getLastEventId()); + $this->assertSame($eventId, SentrySdk::getIsolationScope()->getLastEventId()); ClientReportAggregator::getInstance()->add(DataCategory::profile(), Reason::eventProcessor(), 10); ClientReportAggregator::getInstance()->flush(); - $this->assertSame($eventId, $hub->getLastEventId()); + $this->assertSame($eventId, SentrySdk::getIsolationScope()->getLastEventId()); } public function testNegativeQuantityDiscarded(): void @@ -103,13 +103,13 @@ public function testZeroQuantityDiscarded(): void public function testNegativeQuantityDiscardedWhenNoClientIsBound(): void { - SentrySdk::setCurrentHub(new Hub(new NoOpClient())); + SentrySdk::init(new NoOpClient()); ClientReportAggregator::getInstance()->add(DataCategory::profile(), Reason::eventProcessor(), -10); - SentrySdk::setCurrentHub(new Hub(new Client(new Options([ + SentrySdk::init(new Client(new Options([ 'logger' => StubLogger::getInstance(), - ]), StubTransport::getInstance()))); + ]), StubTransport::getInstance())); ClientReportAggregator::getInstance()->flush(); diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 812c5d12fd..55c6a085ef 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -8,8 +8,11 @@ use Monolog\LogRecord; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; +use Sentry\ClientInterface; +use Sentry\Event; use Sentry\Monolog\BreadcrumbHandler; -use Sentry\State\HubInterface; +use Sentry\Options; +use Sentry\SentrySdk; final class BreadcrumbHandlerTest extends TestCase { @@ -20,22 +23,29 @@ final class BreadcrumbHandlerTest extends TestCase */ public function testHandle($record, Breadcrumb $expectedBreadcrumb): void { - $hub = $this->createMock(HubInterface::class); - $hub->expects($this->once()) - ->method('addBreadcrumb') - ->with($this->callback(function (Breadcrumb $breadcrumb) use ($expectedBreadcrumb): bool { - $this->assertSame($expectedBreadcrumb->getMessage(), $breadcrumb->getMessage()); - $this->assertSame($expectedBreadcrumb->getLevel(), $breadcrumb->getLevel()); - $this->assertSame($expectedBreadcrumb->getType(), $breadcrumb->getType()); - $this->assertEquals($expectedBreadcrumb->getTimestamp(), $breadcrumb->getTimestamp()); - $this->assertSame($expectedBreadcrumb->getCategory(), $breadcrumb->getCategory()); - $this->assertEquals($expectedBreadcrumb->getMetadata(), $breadcrumb->getMetadata()); - - return true; - })); - - $handler = new BreadcrumbHandler($hub); + $client = $this->createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('getOptions') + ->willReturn(new Options()); + + SentrySdk::init($client); + + $handler = new BreadcrumbHandler(); $handler->handle($record); + + $event = Event::createEvent(); + SentrySdk::getIsolationScope()->applyToEvent($event); + + $this->assertCount(1, $event->getBreadcrumbs()); + + $breadcrumb = $event->getBreadcrumbs()[0]; + + $this->assertSame($expectedBreadcrumb->getMessage(), $breadcrumb->getMessage()); + $this->assertSame($expectedBreadcrumb->getLevel(), $breadcrumb->getLevel()); + $this->assertSame($expectedBreadcrumb->getType(), $breadcrumb->getType()); + $this->assertEquals($expectedBreadcrumb->getTimestamp(), $breadcrumb->getTimestamp()); + $this->assertSame($expectedBreadcrumb->getCategory(), $breadcrumb->getCategory()); + $this->assertEquals($expectedBreadcrumb->getMetadata(), $breadcrumb->getMetadata()); } /** diff --git a/tests/Monolog/ExceptionToSentryIssueHandlerTest.php b/tests/Monolog/ExceptionToSentryIssueHandlerTest.php index 20dd681397..839c2db547 100644 --- a/tests/Monolog/ExceptionToSentryIssueHandlerTest.php +++ b/tests/Monolog/ExceptionToSentryIssueHandlerTest.php @@ -11,7 +11,7 @@ use Sentry\ClientInterface; use Sentry\Event; use Sentry\Monolog\ExceptionToSentryIssueHandler; -use Sentry\State\Hub; +use Sentry\SentrySdk; use Sentry\State\Scope; final class ExceptionToSentryIssueHandlerTest extends TestCase @@ -41,7 +41,9 @@ public function testHandleCapturesExceptionAndAddsMetadata($record, \Throwable $ null ); - $handler = new ExceptionToSentryIssueHandler(new Hub($client, new Scope())); + SentrySdk::init($client); + + $handler = new ExceptionToSentryIssueHandler(); $this->assertTrue($handler->isHandling($record)); $handler->handle($record); @@ -57,7 +59,9 @@ public function testHandleReturnsFalseWhenBubblingEnabled(): void ->method('captureException') ->with($this->identicalTo($exception), $this->isInstanceOf(Scope::class), null); - $handler = new ExceptionToSentryIssueHandler(new Hub($client, new Scope()), Logger::WARNING); + SentrySdk::init($client); + + $handler = new ExceptionToSentryIssueHandler(Logger::WARNING); $record = RecordFactory::create( 'foo bar', Logger::WARNING, @@ -82,7 +86,9 @@ public function testHandleReturnsTrueWhenBubblingDisabled(): void ->method('captureException') ->with($this->identicalTo($exception), $this->isInstanceOf(Scope::class), null); - $handler = new ExceptionToSentryIssueHandler(new Hub($client, new Scope()), Logger::WARNING, false); + SentrySdk::init($client); + + $handler = new ExceptionToSentryIssueHandler(Logger::WARNING, false); $record = RecordFactory::create( 'foo bar', Logger::WARNING, @@ -109,7 +115,9 @@ public function testHandleIgnoresRecordsWithoutThrowable($record): void $client->expects($this->never()) ->method('captureException'); - $handler = new ExceptionToSentryIssueHandler(new Hub($client, new Scope()), Logger::DEBUG, false); + SentrySdk::init($client); + + $handler = new ExceptionToSentryIssueHandler(Logger::DEBUG, false); $this->assertTrue($handler->isHandling($record)); $this->assertFalse($handler->handle($record)); @@ -124,7 +132,9 @@ public function testHandleIgnoresRecordsBelowThreshold(): void $client->expects($this->never()) ->method('captureException'); - $handler = new ExceptionToSentryIssueHandler(new Hub($client, new Scope()), Logger::ERROR, false); + SentrySdk::init($client); + + $handler = new ExceptionToSentryIssueHandler(Logger::ERROR, false); $record = RecordFactory::create( 'foo bar', Logger::WARNING, @@ -145,7 +155,7 @@ public function testLegacyIsHandlingUsesMinimalLevelRecord(): void $this->markTestSkipped('Test only works for Monolog < 3'); } - $handler = new ExceptionToSentryIssueHandler(new Hub($this->createMock(ClientInterface::class), new Scope()), Logger::WARNING); + $handler = new ExceptionToSentryIssueHandler(Logger::WARNING); $this->assertTrue($handler->isHandling(['level' => Logger::WARNING])); $this->assertFalse($handler->isHandling(['level' => Logger::INFO])); diff --git a/tests/Monolog/LogToSentryIssueHandlerTest.php b/tests/Monolog/LogToSentryIssueHandlerTest.php index 7bbff023b4..ec9a53d604 100644 --- a/tests/Monolog/LogToSentryIssueHandlerTest.php +++ b/tests/Monolog/LogToSentryIssueHandlerTest.php @@ -14,8 +14,8 @@ use Sentry\EventHint; use Sentry\Monolog\ExceptionToSentryIssueHandler; use Sentry\Monolog\LogToSentryIssueHandler; +use Sentry\SentrySdk; use Sentry\Severity; -use Sentry\State\Hub; use Sentry\State\Scope; use Sentry\Tests\StubTransport; @@ -59,7 +59,9 @@ public function testHandleCapturesLogMessageAsIssue(bool $fillExtraContext, $rec }) ); - $handler = new LogToSentryIssueHandler(new Hub($client, new Scope()), Logger::DEBUG, true, $fillExtraContext); + SentrySdk::init($client); + + $handler = new LogToSentryIssueHandler(Logger::DEBUG, true, $fillExtraContext); $this->assertTrue($handler->isHandling($record)); $this->assertFalse($handler->handle($record)); @@ -73,7 +75,9 @@ public function testHandleReturnsTrueWhenBubblingDisabled(): void ->method('captureEvent') ->with($this->isInstanceOf(Event::class), $this->isInstanceOf(EventHint::class), $this->isInstanceOf(Scope::class)); - $handler = new LogToSentryIssueHandler(new Hub($client, new Scope()), Logger::WARNING, false); + SentrySdk::init($client); + + $handler = new LogToSentryIssueHandler(Logger::WARNING, false); $record = RecordFactory::create('foo bar', Logger::WARNING, 'channel.foo', [], []); $this->assertTrue($handler->isHandling($record)); @@ -87,7 +91,9 @@ public function testHandleIgnoresRecordsWithThrowableExceptionContext(): void $client->expects($this->never()) ->method('captureEvent'); - $handler = new LogToSentryIssueHandler(new Hub($client, new Scope()), Logger::DEBUG, false); + SentrySdk::init($client); + + $handler = new LogToSentryIssueHandler(Logger::DEBUG, false); $record = RecordFactory::create( 'foo bar', Logger::WARNING, @@ -127,7 +133,9 @@ public function testHandleCapturesRecordsWithNonThrowableExceptionContext(): voi }) ); - $handler = new LogToSentryIssueHandler(new Hub($client, new Scope()), Logger::DEBUG, false, true); + SentrySdk::init($client); + + $handler = new LogToSentryIssueHandler(Logger::DEBUG, false, true); $record = RecordFactory::create( 'foo bar', Logger::WARNING, @@ -149,7 +157,9 @@ public function testHandleIgnoresRecordsBelowThreshold(): void $client->expects($this->never()) ->method('captureEvent'); - $handler = new LogToSentryIssueHandler(new Hub($client, new Scope()), Logger::ERROR, false); + SentrySdk::init($client); + + $handler = new LogToSentryIssueHandler(Logger::ERROR, false); $record = RecordFactory::create('foo bar', Logger::WARNING, 'channel.foo', [], []); $this->assertFalse($handler->isHandling($record)); @@ -162,7 +172,7 @@ public function testLegacyIsHandlingUsesMinimalLevelRecord(): void $this->markTestSkipped('Test only works for Monolog < 3'); } - $handler = new LogToSentryIssueHandler(new Hub($this->createMock(ClientInterface::class), new Scope()), Logger::WARNING); + $handler = new LogToSentryIssueHandler(Logger::WARNING); $this->assertTrue($handler->isHandling(['level' => Logger::WARNING])); $this->assertFalse($handler->isHandling(['level' => Logger::INFO])); @@ -173,11 +183,11 @@ public function testLogAndExceptionIssueHandlersReplaceLegacyHandlerUseCases(): $client = ClientBuilder::create() ->setTransport(StubTransport::getInstance()) ->getClient(); - $hub = new Hub($client, new Scope()); + SentrySdk::init($client); $logger = new Logger('channel.foo', [ - new LogToSentryIssueHandler($hub, Logger::WARNING, true, true), - new ExceptionToSentryIssueHandler($hub, Logger::WARNING), + new LogToSentryIssueHandler(Logger::WARNING, true, true), + new ExceptionToSentryIssueHandler(Logger::WARNING), ]); $logger->warning('plain warning', [ diff --git a/tests/Tracing/GuzzleTracingMiddlewareTest.php b/tests/Tracing/GuzzleTracingMiddlewareTest.php index becfa84a8e..e0f3df44ec 100644 --- a/tests/Tracing/GuzzleTracingMiddlewareTest.php +++ b/tests/Tracing/GuzzleTracingMiddlewareTest.php @@ -16,7 +16,6 @@ use Sentry\EventType; use Sentry\Options; use Sentry\SentrySdk; -use Sentry\State\Hub; use Sentry\State\Scope; use Sentry\Tracing\GuzzleTracingMiddleware; use Sentry\Tracing\SpanStatus; @@ -33,16 +32,15 @@ public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void 'traces_sample_rate' => 0, ])); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::init($client); - $transaction = $hub->startTransaction(TransactionContext::make()); + $transaction = \Sentry\startTransaction(TransactionContext::make()); $this->assertFalse($transaction->getSampled()); $expectedPromiseResult = new Response(); - $middleware = GuzzleTracingMiddleware::trace($hub); + $middleware = GuzzleTracingMiddleware::trace(); $function = $middleware(static function () use ($expectedPromiseResult): PromiseInterface { return new FulfilledPromise($expectedPromiseResult); }); @@ -60,13 +58,11 @@ public function testTraceCreatesBreadcrumbIfSpanIsNotSet(): void $this->assertNull($transaction->getSpanRecorder()); - $hub->configureScope(function (Scope $scope): void { - $event = Event::createEvent(); + $event = Event::createEvent(); - $scope->applyToEvent($event); + SentrySdk::getIsolationScope()->applyToEvent($event); - $this->assertCount(1, $event->getBreadcrumbs()); - }); + $this->assertCount(1, $event->getBreadcrumbs()); } public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void @@ -78,16 +74,15 @@ public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void 'traces_sample_rate' => 1, ])); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::init($client); - $transaction = $hub->startTransaction(TransactionContext::make()); + $transaction = \Sentry\startTransaction(TransactionContext::make()); $this->assertTrue($transaction->getSampled()); $expectedPromiseResult = new Response(); - $middleware = GuzzleTracingMiddleware::trace($hub); + $middleware = GuzzleTracingMiddleware::trace(); $function = $middleware(static function () use ($expectedPromiseResult): PromiseInterface { return new FulfilledPromise($expectedPromiseResult); }); @@ -106,13 +101,61 @@ public function testTraceCreatesBreadcrumbIfSpanIsRecorded(): void $this->assertNotNull($transaction->getSpanRecorder()); $this->assertCount(1, $transaction->getSpanRecorder()->getSpans()); - $hub->configureScope(function (Scope $scope): void { - $event = Event::createEvent(); + $event = Event::createEvent(); + + SentrySdk::getIsolationScope()->applyToEvent($event); - $scope->applyToEvent($event); + $this->assertCount(1, $event->getBreadcrumbs()); + } - $this->assertCount(1, $event->getBreadcrumbs()); + public function testTraceUsesProvidedIsolationScope(): void + { + $client = $this->createMock(ClientInterface::class); + $client->expects($this->atLeastOnce()) + ->method('getOptions') + ->willReturn(new Options([ + 'trace_propagation_targets' => [ + 'www.example.com', + ], + ])); + + SentrySdk::init($client); + + $currentScope = SentrySdk::getIsolationScope(); + + $providedScope = new Scope(); + $providedScope->setClient($client); + + $request = new Request('GET', 'https://www.example.com'); + $expectedPromiseResult = new Response(); + + $middleware = GuzzleTracingMiddleware::trace($providedScope); + $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { + $this->assertNotEmpty($request->getHeader('sentry-trace')); + $this->assertNotEmpty($request->getHeader('baggage')); + + return new FulfilledPromise($expectedPromiseResult); }); + + /** @var PromiseInterface $promise */ + $promise = $function($request, []); + + try { + $promiseResult = $promise->wait(); + } catch (\Throwable $exception) { + $promiseResult = $exception; + } + + $this->assertSame($expectedPromiseResult, $promiseResult); + + $currentEvent = Event::createEvent(); + $currentScope->applyToEvent($currentEvent); + + $providedEvent = Event::createEvent(); + $providedScope->applyToEvent($providedEvent); + + $this->assertCount(0, $currentEvent->getBreadcrumbs()); + $this->assertCount(1, $providedEvent->getBreadcrumbs()); } /** @@ -125,12 +168,11 @@ public function testTraceHeaders(Request $request, Options $options, bool $heade ->method('getOptions') ->willReturn($options); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::init($client); $expectedPromiseResult = new Response(); - $middleware = GuzzleTracingMiddleware::trace($hub); + $middleware = GuzzleTracingMiddleware::trace(); $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { if ($headersShouldBePresent) { $this->assertNotEmpty($request->getHeader('sentry-trace')); @@ -157,16 +199,15 @@ public function testTraceHeadersWithTransaction(Request $request, Options $optio ->method('getOptions') ->willReturn($options); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::init($client); - $transaction = $hub->startTransaction(new TransactionContext()); + $transaction = \Sentry\startTransaction(new TransactionContext()); - $hub->setSpan($transaction); + SentrySdk::getIsolationScope()->setSpan($transaction); $expectedPromiseResult = new Response(); - $middleware = GuzzleTracingMiddleware::trace($hub); + $middleware = GuzzleTracingMiddleware::trace(); $function = $middleware(function (Request $request) use ($expectedPromiseResult, $headersShouldBePresent): PromiseInterface { if ($headersShouldBePresent) { $this->assertNotEmpty($request->getHeader('sentry-trace')); @@ -201,11 +242,10 @@ public function testTraceHeadersAreNotAddedWhenExternalPropagationContextIsActiv 'trace_propagation_targets' => null, ])); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::init($client); $expectedPromiseResult = new Response(); - $middleware = GuzzleTracingMiddleware::trace($hub); + $middleware = GuzzleTracingMiddleware::trace(); $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { $this->assertEmpty($request->getHeader('sentry-trace')); $this->assertEmpty($request->getHeader('baggage')); @@ -329,17 +369,15 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec ], ])); - $hub = new Hub($client); - SentrySdk::setCurrentHub($hub); + SentrySdk::init($client); + $scope = SentrySdk::getIsolationScope(); $client->expects($this->once()) ->method('captureEvent') - ->with($this->callback(function (Event $eventArg) use ($hub, $request, $expectedPromiseResult, $expectedBreadcrumbData, $expectedSpanData): bool { + ->with($this->callback(function (Event $eventArg) use ($scope, $request, $expectedPromiseResult, $expectedBreadcrumbData, $expectedSpanData): bool { $this->assertSame(EventType::transaction(), $eventArg->getType()); - $hub->configureScope(static function (Scope $scope) use ($eventArg): void { - $scope->applyToEvent($eventArg); - }); + $scope->applyToEvent($eventArg); $spans = $eventArg->getSpans(); $breadcrumbs = $eventArg->getBreadcrumbs(); @@ -372,11 +410,11 @@ public function testTrace(Request $request, $expectedPromiseResult, array $expec return true; })); - $transaction = $hub->startTransaction(new TransactionContext()); + $transaction = \Sentry\startTransaction(new TransactionContext()); - $hub->setSpan($transaction); + $scope->setSpan($transaction); - $middleware = GuzzleTracingMiddleware::trace($hub); + $middleware = GuzzleTracingMiddleware::trace(); $function = $middleware(function (Request $request) use ($expectedPromiseResult): PromiseInterface { $this->assertNotEmpty($request->getHeader('sentry-trace')); $this->assertNotEmpty($request->getHeader('baggage')); diff --git a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt index fe1cf65e24..5fb8643950 100644 --- a/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt +++ b/tests/phpt/error_handler_captures_errors_not_silencable_on_php_8_and_up.phpt @@ -57,7 +57,7 @@ $client = ClientBuilder::create($options) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); echo 'Triggering "silenced" E_USER_ERROR error' . PHP_EOL; diff --git a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt index 11ffa7c3c8..b0179c1889 100644 --- a/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt +++ b/tests/phpt/error_handler_respects_capture_silenced_errors_option.phpt @@ -50,7 +50,7 @@ $client = ClientBuilder::create($options) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); echo 'Triggering silenced error' . PHP_EOL; diff --git a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt index f6017b8a55..09e0ab2416 100644 --- a/tests/phpt/error_handler_respects_current_error_reporting_level.phpt +++ b/tests/phpt/error_handler_respects_current_error_reporting_level.phpt @@ -56,7 +56,7 @@ $client = ClientBuilder::create($options) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); echo 'Triggering E_USER_NOTICE with error reporting on E_ALL' . PHP_EOL; diff --git a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt index 592ebaa37d..fd610d824c 100644 --- a/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt +++ b/tests/phpt/error_handler_respects_error_types_option_regardless_of_error_reporting.phpt @@ -50,7 +50,7 @@ $client = ClientBuilder::create($options) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); echo 'Triggering E_USER_NOTICE error' . PHP_EOL; diff --git a/tests/phpt/error_listener_integration_respects_error_types_option.phpt b/tests/phpt/error_listener_integration_respects_error_types_option.phpt index 20d413ecde..db00f7b26e 100644 --- a/tests/phpt/error_listener_integration_respects_error_types_option.phpt +++ b/tests/phpt/error_listener_integration_respects_error_types_option.phpt @@ -56,7 +56,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); trigger_error('Error thrown', E_USER_NOTICE); trigger_error('Error thrown', E_USER_WARNING); diff --git a/tests/phpt/php84/error_handler_captures_fatal_error.phpt b/tests/phpt/php84/error_handler_captures_fatal_error.phpt index 9225fbafe1..349c6e6613 100644 --- a/tests/phpt/php84/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/php84/error_handler_captures_fatal_error.phpt @@ -53,7 +53,7 @@ $client = ClientBuilder::create($options) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); $errorHandler = ErrorHandler::registerOnceErrorHandler(); $errorHandler->addErrorHandlerListener(static function (): void { diff --git a/tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt index 7b76f6fedb..9fafb91883 100644 --- a/tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/php84/fatal_error_integration_captures_fatal_error.phpt @@ -58,7 +58,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); final class TestClass implements \JsonSerializable { diff --git a/tests/phpt/php84/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/php84/fatal_error_integration_respects_error_types_option.phpt index 56620b1c03..f215caeef8 100644 --- a/tests/phpt/php84/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/php84/fatal_error_integration_respects_error_types_option.phpt @@ -59,7 +59,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); final class TestClass implements \JsonSerializable { diff --git a/tests/phpt/php85/error_handler_captures_fatal_error.phpt b/tests/phpt/php85/error_handler_captures_fatal_error.phpt index 03f77c5891..594ad434ab 100644 --- a/tests/phpt/php85/error_handler_captures_fatal_error.phpt +++ b/tests/phpt/php85/error_handler_captures_fatal_error.phpt @@ -53,7 +53,7 @@ $client = ClientBuilder::create($options) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); $errorHandler = ErrorHandler::registerOnceErrorHandler(); $errorHandler->addErrorHandlerListener(static function (): void { diff --git a/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt b/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt index b6c62b636c..8911b0b46d 100644 --- a/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt +++ b/tests/phpt/php85/fatal_error_integration_captures_fatal_error.phpt @@ -58,7 +58,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); final class TestClass implements \JsonSerializable { diff --git a/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt b/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt index dbf4e8356f..6928a5de52 100644 --- a/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt +++ b/tests/phpt/php85/fatal_error_integration_respects_error_types_option.phpt @@ -59,7 +59,7 @@ $client = (new ClientBuilder($options)) ->setTransport($transport) ->getClient(); -SentrySdk::getCurrentHub()->bindClient($client); +SentrySdk::init($client); final class TestClass implements \JsonSerializable {