From 67042e4732bfd9c2d1d092a64a74ae89b82457e2 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 13:03:43 +0100 Subject: [PATCH 1/7] Improve `sentry:test` command This now has three different modes: - for testing message capturing - for testing exception handling - for testing error handling --- Classes/Command/SentryCommandController.php | 45 +++++++++++- Classes/Test/JsonSerializableTestArgument.php | 2 +- README.md | 69 +++++++++++-------- 3 files changed, 84 insertions(+), 32 deletions(-) diff --git a/Classes/Command/SentryCommandController.php b/Classes/Command/SentryCommandController.php index 043f3ee..a95f7c8 100644 --- a/Classes/Command/SentryCommandController.php +++ b/Classes/Command/SentryCommandController.php @@ -23,6 +23,10 @@ final class SentryCommandController extends CommandController { + const TEST_MODE_MESSAGE = 'message'; + const TEST_MODE_THROW = 'throw'; + const TEST_MODE_ERROR = 'error'; + /** * @Flow\Inject * @var SentryClient @@ -39,13 +43,12 @@ final class SentryCommandController extends CommandController * * @throws SentryClientTestException */ - public function testCommand(): void + public function testCommand(string $mode = self::TEST_MODE_THROW): void { $this->output->outputLine('Testing Sentry setup …'); $this->output->outputLine('Using the following configuration:'); $options = $this->sentryClient->getOptions(); - $this->output->outputTable([ ['DSN', $options->getDsn()], ['Environment', $options->getEnvironment()], @@ -57,6 +60,23 @@ public function testCommand(): void 'Value' ]); + switch ($mode) { + case self::TEST_MODE_MESSAGE: + $this->captureMessage(); + break; + case self::TEST_MODE_THROW: + $this->throwException(); + break; + case self::TEST_MODE_ERROR: + $this->triggerError(); + break; + default: + $this->output->outputLine('Unknown mode given'); + } + } + + private function captureMessage(): void + { $eventId = $this->sentryClient->captureMessage( 'Flownative Sentry Plugin Test', Severity::debug(), @@ -65,11 +85,30 @@ public function testCommand(): void ] ); + $this->outputLine(); $this->outputLine('An informational message was sent to Sentry Event ID: #%s', [$eventId]); $this->outputLine(); + } + + private function throwException(): void + { + $this->outputLine(); $this->outputLine('This command will now throw an exception for testing purposes.'); $this->outputLine(); - (new ThrowingClass())->throwException(new StringableTestArgument((string)M_PI)); } + + private function triggerError(): void + { + $this->outputLine(); + $this->outputLine('This command will now cause a return type error for testing purposes.'); + $this->outputLine(); + + $function = static function (): int { + /** @noinspection PhpStrictTypeCheckingInspection */ + return 'wrong type'; + }; + /** @noinspection PhpExpressionResultUnusedInspection */ + $function(); + } } diff --git a/Classes/Test/JsonSerializableTestArgument.php b/Classes/Test/JsonSerializableTestArgument.php index 72cdcec..ad26c11 100644 --- a/Classes/Test/JsonSerializableTestArgument.php +++ b/Classes/Test/JsonSerializableTestArgument.php @@ -22,7 +22,7 @@ public function __construct(int $value) $this->value = $value; } - public function jsonSerialize() + public function jsonSerialize(): int { return $this->value; } diff --git a/README.md b/README.md index f8b0402..d0e0e34 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,13 @@ reporting of errors to [Sentry](https://www.sentry.io) ## Key Features -This package makes sure that exceptions and errors logged by the Flow -framework also end up in Sentry. This client takes some extra care to -clean up paths and filenames of stacktraces so you get good overview -while looking at an issue in the Sentry UI. +This package makes sure that throwables and exceptions logged in a Flow +application also end up in Sentry. This is done by implementing Flow's +`ThrowableStorageInterface` and configuring the default implementation. + +This packages takes some extra care to clean up paths and filenames of +stacktraces so you get a good overview while looking at an issue in the +Sentry UI. ## Installation @@ -29,7 +32,13 @@ $ composer require flownative/sentry You need to at least specify a DSN to be used as a logging target. Apart from that, you can configure the Sentry environment and release. These options can either be set in the Flow settings or, more conveniently, by -setting the respective environment variables. +setting the respective environment variables: + +- `SENTRY_DSN` +- `SENTRY_ENVIRONMENT` +- `SENTRY_RELEASE` + +The package uses these environment variables by default in the settings: ```yaml Flownative: @@ -71,12 +80,12 @@ similar to the following message: ## Additional Data -Exceptions declared in an application can optionally implement -`WithAdditionalDataInterface` provided by this package. If they do, the -array returned by `getAdditionalData()` will be visible in the "additional +Exceptions declared in an application can optionally implement +`WithAdditionalDataInterface` provided by this package. If they do, the +array returned by `getAdditionalData()` will be visible in the "additional data" section in Sentry. -Note that the array must only contain values of simple types, such as +Note that the array must only contain values of simple types, such as strings, booleans or integers. ## Testing the Client @@ -90,35 +99,39 @@ Run the following command in your terminal to test your configuration: ./flow sentry:test Testing Sentry setup … Using the following configuration: -+-------------+------------------------------------------------------------+ -| Option | Value | -+-------------+------------------------------------------------------------+ ++-------------+----------------------------------------------------------+ +| Option | Value | ++-------------+----------------------------------------------------------+ | DSN | https://abc123456789abcdef1234567890ab@sentry.io/1234567 | -| Environment | development | -| Release | dev | -| Server Name | test_container | -| Sample Rate | 1 | -+-------------+------------------------------------------------------------+ -An informational message was sent to Sentry Event ID: #587abc123457abcd8f873b4212345678 +| Environment | development | +| Release | dev | +| Server Name | test_container | +| Sample Rate | 1 | ++-------------+----------------------------------------------------------+ This command will now throw an exception for testing purposes. -Test exception in SentryCommandController +Test exception in ThrowingClass - Type: Flownative\Sentry\Exception\SentryClientTestException - Code: 1614759519 + Type: Flownative\Sentry\Test\SentryClientTestException + Code: 1662712736 File: Data/Temporary/Development/SubContextBeach/SubContextInstance/Cache/Code/Fl - ow_Object_Classes/Flownative_Sentry_Command_SentryCommandController.php - Line: 79 + ow_Object_Classes/Flownative_Sentry_Test_ThrowingClass.php + Line: 41 Nested exception: -Test "previous" exception thrown by the SentryCommandController +Test "previous" exception in ThrowingClass Type: RuntimeException - Code: 1614759554 + Code: 1662712735 File: Data/Temporary/Development/SubContextBeach/SubContextInstance/Cache/Code/Fl - ow_Object_Classes/Flownative_Sentry_Command_SentryCommandController.php - Line: 78 + ow_Object_Classes/Flownative_Sentry_Test_ThrowingClass.php + Line: 40 -Open Data/Logs/Exceptions/2021030308325919ecbf.txt for a full stack trace. +Open Data/Logs/Exceptions/202411181211403b652e.txt for a full stack trace. ``` + +There are two more test modes for message capturing and error handling: + +- `./flow sentry:test --mode message` +- `./flow sentry:test --mode error` From 5cd55ba46fae862bcbb77468d5ee86beb4036864 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 13:07:12 +0100 Subject: [PATCH 2/7] Register Sentry early in the bootstrap Instead of setting up the Sentry client when something needs to be sent to Sentry in the `SentryStorage` this initializes the Sentry client as soon as Flow's object management is usable. This allows to use Sentry to capture exceptions and errors as early as possible. --- Classes/Package.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Classes/Package.php diff --git a/Classes/Package.php b/Classes/Package.php new file mode 100644 index 0000000..ab84ffd --- /dev/null +++ b/Classes/Package.php @@ -0,0 +1,24 @@ +getSignalSlotDispatcher(); + + $dispatcher->connect(Sequence::class, 'afterInvokeStep', function ($step) { + if ($step->getIdentifier() === 'neos.flow:objectmanagement:runtime') { + // instantiate client to set up Sentry and register error handler early + /** @noinspection PhpExpressionResultUnusedInspection */ + new SentryClient(); + } + }); + } +} From 6e93affb665afc3077aa8cc00ed751db9ad19e9b Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 14:26:50 +0100 Subject: [PATCH 3/7] Use Sentry default integrations This activates the default integrations shipped with Sentry again. This adds information about installed Composer packages, the request, frame context and environment. Through this, some "custom" tags can be dropped, as the information is included by the default integrations. Since Sentry registers an error handler, errors not handled by the exception handler will also be recorded in Sentry. --- Classes/SentryClient.php | 42 +++++++++------------------------------- composer.json | 3 +-- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/Classes/SentryClient.php b/Classes/SentryClient.php index 135a54b..25bbf63 100644 --- a/Classes/SentryClient.php +++ b/Classes/SentryClient.php @@ -17,8 +17,6 @@ use Flownative\Sentry\Context\UserContextServiceInterface; use Flownative\Sentry\Context\WithExtraDataInterface; use Flownative\Sentry\Log\CaptureResult; -use GuzzleHttp\Psr7\ServerRequest; -use Jenssegers\Agent\Agent; use Neos\Flow\Annotations as Flow; use Neos\Flow\Core\Bootstrap; use Neos\Flow\Error\WithReferenceCodeInterface; @@ -31,7 +29,6 @@ use Neos\Flow\Utility\Environment; use Neos\Utility\Arrays; use Psr\Log\LoggerInterface; -use Psr\Log\LogLevel; use Sentry\Event; use Sentry\EventHint; use Sentry\EventId; @@ -93,6 +90,10 @@ public function injectSettings(array $settings): void public function initializeObject(): void { + if (empty($this->dsn)) { + return; + } + $representationSerializer = new RepresentationSerializer( new Options([]) ); @@ -102,10 +103,6 @@ public function initializeObject(): void $representationSerializer ); - if (empty($this->dsn)) { - return; - } - \Sentry\init([ 'dsn' => $this->dsn, 'environment' => $this->environment, @@ -118,8 +115,7 @@ public function initializeObject(): void FLOW_PATH_ROOT . '/Packages/Framework/Neos.Flow/Classes/Log/', FLOW_PATH_ROOT . '/Packages/Libraries/neos/flow-log/' ], - 'default_integrations' => false, - 'attach_stacktrace' => true + 'attach_stacktrace' => true, ]); $client = SentrySdk::getCurrentHub()->getClient(); @@ -136,38 +132,18 @@ private function setTags(): void try { $flowPackage = $this->packageManager->getPackage('Neos.Flow'); $flowVersion = $flowPackage->getInstalledVersion(); - } catch (UnknownPackageException $e) { + } catch (UnknownPackageException) { } } if (empty($flowVersion)) { $flowVersion = FLOW_VERSION_BRANCH; } - $currentSession = null; - if ($this->sessionManager) { - $currentSession = $this->sessionManager->getCurrentSession(); - } + $currentSession = $this->sessionManager?->getCurrentSession(); SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) use ($flowVersion, $currentSession): void { $scope->setTag('flow_version', $flowVersion); $scope->setTag('flow_context', (string)Bootstrap::$staticObjectManager->get(Environment::class)->getContext()); - $scope->setTag('php_version', PHP_VERSION); - - if (PHP_SAPI !== 'cli') { - $scope->setTag('uri', - (string)ServerRequest::fromGlobals()->getUri()); - - $agent = new Agent(); - $scope->setContext('client_os', [ - 'name' => $agent->platform(), - 'version' => $agent->version($agent->platform()) - ]); - - $scope->setContext('client_browser', [ - 'name' => $agent->browser(), - 'version' => $agent->version($agent->browser()) - ]); - } if ($currentSession instanceof Session && $currentSession->isStarted()) { $scope->setTag('flow_session_sha1', sha1($currentSession->getId())); @@ -226,7 +202,7 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar $message = 'ignored'; } return new CaptureResult( - true, + true, $message, (string)$sentryEventId ); @@ -326,7 +302,7 @@ private function prepareStacktrace(\Throwable $throwable = null): Stacktrace $frame->getRawFunctionName(), $frame->getAbsoluteFilePath(), $frame->getVars(), - strpos($classPathAndFilename, 'Packages/Framework/') === false + !str_contains($classPathAndFilename, 'Packages/Framework/') ); } return new Stacktrace($frames); diff --git a/composer.json b/composer.json index 2527c06..62efd54 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,7 @@ "ext-json": "*", "php": "^8.1", "neos/flow": "^8.0 || ^9.0 || @dev", - "sentry/sentry": "^4.0", - "jenssegers/agent": "^2.6" + "sentry/sentry": "^4.0" }, "autoload": { "psr-4": { From f07e071a16685eb776976539b6dc87d67aba3c54 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 14:28:46 +0100 Subject: [PATCH 4/7] "Re-tag" when capturing to Sentry There may have been changes after Sentry was initialised first, so re-run `setTags()` when sending events to Sentry. --- Classes/SentryClient.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Classes/SentryClient.php b/Classes/SentryClient.php index 25bbf63..91968b9 100644 --- a/Classes/SentryClient.php +++ b/Classes/SentryClient.php @@ -189,6 +189,7 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar $captureException = (!in_array(get_class($throwable), $this->excludeExceptionTypes, true)); if ($captureException) { + $this->setTags(); $this->configureScope($extraData, $tags); if ($throwable instanceof Exception && $throwable->getStatusCode() === 404) { SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope): void { @@ -225,6 +226,7 @@ public function captureMessage(string $message, Severity $severity, array $extra $eventHint = EventHint::fromArray([ 'stacktrace' => $this->prepareStacktrace() ]); + $this->setTags(); $sentryEventId = \Sentry\captureMessage($message, $severity, $eventHint); if ($this->logger) { From 0fd62e34ddbb4d8d4cc540b392dbf869f50ffe0c Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 20:19:10 +0100 Subject: [PATCH 5/7] Add log entries as breadcrumbs for Sentry events --- Classes/Log/SentryFileBackend.php | 64 ++++++++++++++++++++----------- Configuration/Settings.yaml | 7 +++- README.md | 22 +++++++++++ 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/Classes/Log/SentryFileBackend.php b/Classes/Log/SentryFileBackend.php index 5a02e68..b6b09ef 100644 --- a/Classes/Log/SentryFileBackend.php +++ b/Classes/Log/SentryFileBackend.php @@ -15,14 +15,13 @@ use Flownative\Sentry\SentryClientTrait; use Neos\Flow\Log\Backend\FileBackend; -use Sentry\Severity; +use Sentry\Breadcrumb; +use Sentry\SentrySdk; class SentryFileBackend extends FileBackend { use SentryClientTrait; - private bool $capturingMessage = false; - /** * Appends the given message along with the additional information into the log. * @@ -36,29 +35,50 @@ class SentryFileBackend extends FileBackend */ public function append(string $message, int $severity = LOG_INFO, $additionalData = null, ?string $packageKey = null, ?string $className = null, ?string $methodName = null): void { - if ($this->capturingMessage) { - return; + try { + SentrySdk::getCurrentHub()->addBreadcrumb( + new Breadcrumb( + $this->getBreadcrumbLevel($severity), + $this->getBreadcrumbType($severity), + basename($this->logFileUrl), + $message, + ($additionalData ?? []) + array_filter([ + 'packageKey' => $packageKey, 'className' => $className, 'methodName' => $methodName + ]), + time() + ) + ); + } catch (\Throwable $throwable) { + parent::append( + sprintf('%s (%s)', $throwable->getMessage(), $throwable->getCode()), + LOG_WARNING, + null, + 'Flownative.Sentry', + __CLASS__, + __METHOD__ + ); } - try { - $this->capturingMessage = true; + parent::append($message, $severity, $additionalData, $packageKey, $className, $methodName); + } - $sentryClient = self::getSentryClient(); - if ($severity <= LOG_NOTICE && $sentryClient) { - $sentrySeverity = match ($severity) { - LOG_WARNING => Severity::warning(), - LOG_ERR => Severity::error(), - LOG_CRIT, LOG_ALERT, LOG_EMERG => Severity::fatal(), - default => Severity::info(), - }; + private function getBreadcrumbLevel(int $severity): string + { + return match ($severity) { + LOG_EMERG, LOG_ALERT, LOG_CRIT => Breadcrumb::LEVEL_FATAL, + LOG_ERR => Breadcrumb::LEVEL_ERROR, + LOG_WARNING => Breadcrumb::LEVEL_WARNING, + LOG_NOTICE, LOG_INFO => Breadcrumb::LEVEL_INFO, + default => Breadcrumb::LEVEL_DEBUG, + }; + } - $sentryClient->captureMessage($message, $sentrySeverity, ['Additional Data' => $additionalData]); - } - parent::append($message, $severity, $additionalData, $packageKey, $className, $methodName); - } catch (\Throwable $throwable) { - echo sprintf('SentryFileBackend: %s (%s)', $throwable->getMessage(), $throwable->getCode()); - } finally { - $this->capturingMessage = false; + private function getBreadcrumbType(int $severity): string + { + if ($severity >= LOG_ERR) { + return Breadcrumb::TYPE_ERROR; } + + return Breadcrumb::TYPE_DEFAULT; } } diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 24b4687..63abb36 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -10,8 +10,11 @@ Flownative: Neos: Flow: log: - systemLogger: - backend: Flownative\Sentry\Log\SentryFileBackend + psr3: + Neos\Flow\Log\PsrLoggerFactory: + systemLogger: + default: + class: 'Flownative\Sentry\Log\SentryFileBackend' throwables: storageClass: 'Flownative\Sentry\Log\SentryStorage' optionsByImplementation: diff --git a/README.md b/README.md index d0e0e34..8406f33 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,28 @@ data" section in Sentry. Note that the array must only contain values of simple types, such as strings, booleans or integers. +## Logging integration + +### Breadcrumb handler + +This package configures a logging backend to add messages as breadcrumbs to +be sent to Sentry when an exception happens. This provides more information +on what happened before an exception. + +For more information on breadcrumbs see the Sentry documentation at +https://docs.sentry.io/platforms/php/enriching-events/breadcrumbs/ + +### Monolog + +In case you want to store all log messages in Sentry, one way is to configure +Flow to use monolog for logging and then add the `Sentry\Monolog\Handler` to +the setup. + +Keep in mind that the breadcrumb handler provided by this package might be +disabled when doing this, depending on your configuration. Sentry provides +a monolog integration for that purpose, see `Sentry\Monolog\BreadcrumbHandler` +and https://docs.sentry.io/platforms/php/integrations/monolog/. + ## Testing the Client This package provides a command controller which allows you to log a From b9ae6f106cced39d7023f7b08e95f35d95a8b209 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 20:28:48 +0100 Subject: [PATCH 6/7] Align return type of `captureMessage` to `captureThrowable` --- Classes/Command/SentryCommandController.php | 8 +++-- Classes/SentryClient.php | 36 ++++++++------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Classes/Command/SentryCommandController.php b/Classes/Command/SentryCommandController.php index a95f7c8..05cd3fc 100644 --- a/Classes/Command/SentryCommandController.php +++ b/Classes/Command/SentryCommandController.php @@ -77,7 +77,7 @@ public function testCommand(string $mode = self::TEST_MODE_THROW): void private function captureMessage(): void { - $eventId = $this->sentryClient->captureMessage( + $captureResult = $this->sentryClient->captureMessage( 'Flownative Sentry Plugin Test', Severity::debug(), [ @@ -86,7 +86,11 @@ private function captureMessage(): void ); $this->outputLine(); - $this->outputLine('An informational message was sent to Sentry Event ID: #%s', [$eventId]); + if ($captureResult->suceess) { + $this->outputLine('An informational message was sent to Sentry Event ID: #%s', [$captureResult->eventId]); + } else { + $this->outputLine('Sending an informational message to Sentry failed: %s', [$captureResult->message]); + } $this->outputLine(); } diff --git a/Classes/SentryClient.php b/Classes/SentryClient.php index 91968b9..c350bba 100644 --- a/Classes/SentryClient.php +++ b/Classes/SentryClient.php @@ -209,38 +209,28 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar ); } - public function captureMessage(string $message, Severity $severity, array $extraData = [], array $tags = []): ?EventId + public function captureMessage(string $message, Severity $severity, array $extraData = [], array $tags = []): CaptureResult { if (empty($this->dsn)) { - if ($this->logger) { - $this->logger->warning('Sentry: Failed capturing message, because no Sentry DSN was set. Please check your settings.'); - } - return null; - } - - if (preg_match('/Sentry: [0-9a-f]{32}/', $message) === 1) { - return null; + return new CaptureResult( + false, + 'Failed capturing message, because no Sentry DSN was set. Please check your settings.', + '' + ); } + $this->setTags(); $this->configureScope($extraData, $tags); $eventHint = EventHint::fromArray([ 'stacktrace' => $this->prepareStacktrace() ]); - $this->setTags(); - $sentryEventId = \Sentry\captureMessage($message, $severity, $eventHint); - - if ($this->logger) { - $this->logger->log( - (string)$severity, - sprintf( - '%s (Sentry: %s)', - $message, - $sentryEventId - ) - ); - } + $sentryEventId = SentrySdk::getCurrentHub()->captureMessage($message, $severity, $eventHint); - return $sentryEventId; + return new CaptureResult( + true, + $message, + (string)$sentryEventId + ); } private function configureScope(array $extraData, array $tags): void From 1e020ad2f38cd75026505b2bca87b898c601cd99 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Mon, 18 Nov 2024 21:11:29 +0100 Subject: [PATCH 7/7] Refactor excludeExceptionTypes handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes this a list of boolean values with the excluded types as the keys. The default now is a list of Flow exceptions that declare a 404 status code – those were half-heartedly ignored before this change. --- Classes/SentryClient.php | 45 +++++++++++++++++++------------------ Configuration/Settings.yaml | 7 +++++- README.md | 18 +++++++++------ 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/Classes/SentryClient.php b/Classes/SentryClient.php index c350bba..55eea0a 100644 --- a/Classes/SentryClient.php +++ b/Classes/SentryClient.php @@ -113,7 +113,7 @@ public function initializeObject(): void FLOW_PATH_ROOT . '/Packages/Framework/Neos.Flow/Classes/Aop/', FLOW_PATH_ROOT . '/Packages/Framework/Neos.Flow/Classes/Error/', FLOW_PATH_ROOT . '/Packages/Framework/Neos.Flow/Classes/Log/', - FLOW_PATH_ROOT . '/Packages/Libraries/neos/flow-log/' + FLOW_PATH_ROOT . '/Packages/Libraries/neos/flow-log/', ], 'attach_stacktrace' => true, ]); @@ -164,13 +164,18 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar if (empty($this->dsn)) { return new CaptureResult( false, - 'Failed capturing message, because no Sentry DSN was set. Please check your settings.', + 'Failed capturing throwable, because no Sentry DSN was set. Please check your settings.', '' ); } - $message = ''; - $sentryEventId = ''; + if ($this->excludeException($throwable)) { + return new CaptureResult( + true, + 'Skipped capturing throwable, it is in excludeExceptionTypes', + '' + ); + } if ($throwable instanceof WithReferenceCodeInterface) { $extraData['Reference Code'] = $throwable->getReferenceCode(); @@ -187,24 +192,15 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar $tags['exception_code'] = (string)$throwable->getCode(); - $captureException = (!in_array(get_class($throwable), $this->excludeExceptionTypes, true)); - if ($captureException) { - $this->setTags(); - $this->configureScope($extraData, $tags); - if ($throwable instanceof Exception && $throwable->getStatusCode() === 404) { - SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope): void { - $scope->setLevel(Severity::warning()); - }); - } - $event = Event::createEvent(); - $this->addThrowableToEvent($throwable, $event); - $sentryEventId = SentrySdk::getCurrentHub()->captureEvent($event); - } else { - $message = 'ignored'; - } + $this->setTags(); + $this->configureScope($extraData, $tags); + $event = Event::createEvent(); + $this->addThrowableToEvent($throwable, $event); + $sentryEventId = SentrySdk::getCurrentHub()->captureEvent($event); + return new CaptureResult( true, - $message, + '', (string)$sentryEventId ); } @@ -222,7 +218,7 @@ public function captureMessage(string $message, Severity $severity, array $extra $this->setTags(); $this->configureScope($extraData, $tags); $eventHint = EventHint::fromArray([ - 'stacktrace' => $this->prepareStacktrace() + 'stacktrace' => $this->prepareStacktrace(), ]); $sentryEventId = SentrySdk::getCurrentHub()->captureMessage($message, $severity, $eventHint); @@ -233,6 +229,12 @@ public function captureMessage(string $message, Severity $severity, array $extra ); } + private function excludeException(\Throwable $throwable): bool + { + $excludedExceptions = array_keys(array_filter($this->excludeExceptionTypes)); + return in_array(get_class($throwable), $excludedExceptions, true); + } + private function configureScope(array $extraData, array $tags): void { $securityContext = Bootstrap::$staticObjectManager->get(SecurityContext::class); @@ -250,7 +252,6 @@ private function configureScope(array $extraData, array $tags): void $scope->setTag($tagKey, $tagValue); } $scope->setUser($userContext->toArray()); - $scope->setLevel(null); }); } diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 63abb36..f9b7559 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -5,7 +5,12 @@ Flownative: release: "%env:SENTRY_RELEASE%" sampleRate: 1.0 capture: - excludeExceptionTypes: [] + excludeExceptionTypes: + 'Neos\Flow\Mvc\Controller\Exception\InvalidControllerException': true + 'Neos\Flow\Mvc\Exception\NoMatchingRouteException': true + 'Neos\Flow\Mvc\Exception\NoSuchActionException': true + 'Neos\Flow\Mvc\Exception\NoSuchControllerException': true + 'Neos\Flow\Property\Exception\TargetNotFoundException': true Neos: Flow: diff --git a/README.md b/README.md index 8406f33..02ceb0b 100644 --- a/README.md +++ b/README.md @@ -60,22 +60,26 @@ The default is 1 – 100% percent of all errors are sampled. Throwables (that includes exceptions and runtime errors) are logged as Sentry events. You may specify a list of exceptions which should not be -recorded. If such an exception is thrown, it will only be logged as a -"notice". +recorded. ```yaml Flownative: Sentry: capture: excludeExceptionTypes: - - 'Neos\Flow\Mvc\Controller\Exception\InvalidControllerException' + 'Neos\Flow\Mvc\Controller\Exception\InvalidControllerException': true ``` -If an ignored exception is handled by this Sentry client, it is logged -similar to the following message: +By default all Flow exceptions with a status code of 404 are ignored. in case +you want to see those in Sentry, you can include them case-by-case like so: -``` -… NOTICE Exception 12345: The exception message (Ref: 202004161706040c28ae | Sentry: ignored) +```yaml +Flownative: + Sentry: + capture: + excludeExceptionTypes: + 'Neos\Flow\Mvc\Controller\Exception\InvalidControllerException': false + 'Neos\Flow\Mvc\Exception\NoSuchControllerException': false ``` ## Additional Data