diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c3ddeb163..a4f7cfdc8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -150,6 +150,16 @@ parameters: count: 1 path: src/Options.php + - + message: "#^Method Sentry\\\\Options\\:\\:getIgnoreExceptions\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + + - + message: "#^Method Sentry\\\\Options\\:\\:getIgnoreTransactions\\(\\) should return array\\ but returns mixed\\.$#" + count: 1 + path: src/Options.php + - message: "#^Method Sentry\\\\Options\\:\\:getInAppExcludedPaths\\(\\) should return array\\ but returns mixed\\.$#" count: 1 diff --git a/src/Client.php b/src/Client.php index 3d090cac9..a324c1153 100644 --- a/src/Client.php +++ b/src/Client.php @@ -281,6 +281,12 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco return null; } + $event = $this->applyIgnoreOptions($event); + + if (null === $event) { + return null; + } + if (null !== $scope) { $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint); @@ -311,6 +317,47 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco return $event; } + private function applyIgnoreOptions(Event $event): ?Event + { + if ($event->getType() === EventType::event()) { + $exceptions = $event->getExceptions(); + + if (empty($exceptions)) { + return $event; + } + + foreach ($exceptions as $exception) { + if (\in_array($exception->getType(), $this->options->getIgnoreExceptions(), true)) { + $this->logger->info( + 'The event will be discarded because it matches an entry in "ignore_exceptions".', + ['event' => $event] + ); + + return null; + } + } + } + + if ($event->getType() === EventType::transaction()) { + $transactionName = $event->getTransaction(); + + if (null === $transactionName) { + return $event; + } + + if (\in_array($transactionName, $this->options->getIgnoreTransactions(), true)) { + $this->logger->info( + 'The event will be discarded because it matches a entry in "ignore_transactions".', + ['event' => $event] + ); + + return null; + } + } + + return $event; + } + private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event { if ($event->getType() === EventType::event()) { diff --git a/src/Integration/IgnoreErrorsIntegration.php b/src/Integration/IgnoreErrorsIntegration.php index aae22e10b..7264be103 100644 --- a/src/Integration/IgnoreErrorsIntegration.php +++ b/src/Integration/IgnoreErrorsIntegration.php @@ -13,6 +13,8 @@ * This integration decides whether an event should not be captured according * to a series of options that must match with its data. * + * @deprecated since version 3.17, to be removed in 4.0. Use the `ignore_exceptions` option instead + * * @author Stefano Arlandini * * @psalm-type IntegrationOptions array{ diff --git a/src/Options.php b/src/Options.php index dbee7d128..fd216cb76 100644 --- a/src/Options.php +++ b/src/Options.php @@ -416,6 +416,50 @@ public function setServerName(string $serverName): void $this->options = $this->resolver->resolve($options); } + /** + * Gets a list of exceptions to be ignored and not sent to Sentry. + * + * @return string[] + */ + public function getIgnoreExceptions(): array + { + return $this->options['ignore_exceptions']; + } + + /** + * Sets a list of exceptions to be ignored and not sent to Sentry. + * + * @param string[] $ignoreErrors The list of exceptions to be ignored + */ + public function setIgnoreExceptions(array $ignoreErrors): void + { + $options = array_merge($this->options, ['ignore_exceptions' => $ignoreErrors]); + + $this->options = $this->resolver->resolve($options); + } + + /** + * Gets a list of transaction names to be ignored and not sent to Sentry. + * + * @return string[] + */ + public function getIgnoreTransactions(): array + { + return $this->options['ignore_transactions']; + } + + /** + * Sets a list of transaction names to be ignored and not sent to Sentry. + * + * @param string[] $ignoreTransaction The list of transaction names to be ignored + */ + public function setIgnoreTransactions(array $ignoreTransaction): void + { + $options = array_merge($this->options, ['ignore_transactions' => $ignoreTransaction]); + + $this->options = $this->resolver->resolve($options); + } + /** * Gets a callback that will be invoked before an event is sent to the server. * If `null` is returned it won't be sent. @@ -872,6 +916,8 @@ private function configureOptions(OptionsResolver $resolver): void 'release' => $_SERVER['SENTRY_RELEASE'] ?? null, 'dsn' => $_SERVER['SENTRY_DSN'] ?? null, 'server_name' => gethostname(), + 'ignore_exceptions' => [], + 'ignore_transactions' => [], 'before_send' => static function (Event $event): Event { return $event; }, @@ -916,6 +962,8 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('server_name', 'string'); $resolver->setAllowedTypes('before_send', ['callable']); $resolver->setAllowedTypes('before_send_transaction', ['callable']); + $resolver->setAllowedTypes('ignore_exceptions', 'string[]'); + $resolver->setAllowedTypes('ignore_transactions', 'string[]'); $resolver->setAllowedTypes('trace_propagation_targets', 'string[]'); $resolver->setAllowedTypes('tags', 'string[]'); $resolver->setAllowedTypes('error_types', ['null', 'int']); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 078ddc1b1..f5cf5beac 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -587,6 +587,53 @@ public function processEventDiscardsEventWhenItIsSampledDueToSampleRateOptionDat ]; } + public function testProcessEventDiscardsEventWhenIgnoreExceptionsMatches(): void + { + $exception = new \Exception('Some foo error'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because it matches an entry in "ignore_exceptions".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'ignore_exceptions' => [\Exception::class], + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureException($exception); + } + + public function testProcessEventDiscardsEventWhenIgnoreTransactionsMatches(): void + { + $event = Event::createTransaction(); + $event->setTransaction('GET /foo'); + + /** @var LoggerInterface&MockObject $logger */ + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('info') + ->with('The event will be discarded because it matches a entry in "ignore_transactions".', $this->callback(static function (array $context): bool { + return isset($context['event']) && $context['event'] instanceof Event; + })); + + $options = [ + 'ignore_transactions' => ['GET /foo'], + ]; + + $client = ClientBuilder::create($options) + ->setLogger($logger) + ->getClient(); + + $client->captureEvent($event); + } + public function testProcessEventDiscardsEventWhenBeforeSendCallbackReturnsNull(): void { /** @var LoggerInterface&MockObject $logger */ diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index c58424878..178165b6c 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -266,6 +266,24 @@ static function (): void {}, null, ]; + yield [ + 'ignore_exceptions', + ['foo', 'bar'], + 'getIgnoreExceptions', + 'setIgnoreExceptions', + null, + null, + ]; + + yield [ + 'ignore_transactions', + ['foo', 'bar'], + 'getIgnoreTransactions', + 'setIgnoreTransactions', + null, + null, + ]; + yield [ 'before_send', static function (): void {},