diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31ec9023..462f9b9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,85 @@
# Changelog
-## Unreleased
+## 4.6.0
+
+The Sentry SDK team is happy to announce the immediate availability of Sentry Symfony SDK v4.6.0.
+This release contains a colorful bouquet of new features.
+
+### Features
+
+- Report exceptions to Sentry as unhandled by default [(#674)](https://github.com/getsentry/sentry-symfony/pull/674)
+
+ All unhandled exceptions will now be marked as `handled: false`. You can query for such events on the issues list page,
+ by searching for `error.handled:false`.
+
+- Exceptions from messages which will be retried are sent to Sentry as handled [(#674)](https://github.com/getsentry/sentry-symfony/pull/674)
+
+ All unhandled exceptions happening on the message bus will now be unpacked and reported individually.
+ The `WorkerMessageFailedEvent::willRetry` property is used to determine the `handled` value of the event sent to Sentry.
+
+- Add `register_error_handler` config option [(#687)](https://github.com/getsentry/sentry-symfony/pull/687)
+
+ With this option, you can disable the global error and exception handlers of the base PHP SDK.
+ If disabled, only events logged by Monolog will be sent to Sentry.
+
+ ```yaml
+ sentry:
+ dsn: '%env(SENTRY_DSN)%'
+ register_error_listener: false
+ register_error_handler: false
+
+ monolog:
+ handlers:
+ sentry:
+ type: sentry
+ level: !php/const Monolog\Logger::ERROR
+ hub_id: Sentry\State\HubInterface
+ ```
+
+- Add `before_send_transaction` [(#691)](https://github.com/getsentry/sentry-symfony/pull/691)
+
+ Similar to `before_send`, you can now apply additional logic for `transaction` events.
+ You can mutate the `transaction` event before it is sent to Sentry. If your callback returns `null`,
+ the event is dropped.
+
+ ```yaml
+ sentry:
+ options:
+ before_send_transaction: 'sentry.callback.before_send_transaction'
+
+ services:
+ sentry.callback.before_send_transaction:
+ class: 'App\Service\Sentry'
+ factory: [ '@App\Service\Sentry', 'getBeforeSendTransaction' ]
+ ```
+
+ ```php
+ >`.
+
+ You may need to update your starred transactions as well as your dashboards due to this change.
+
+### Bug Fixes
+
+- Sanatize HTTP client spans [(#690)](https://github.com/getsentry/sentry-symfony/pull/690)
## 4.5.0 (2022-11-28)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2898c4b0..b55d1a0b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -19,7 +19,6 @@ If you feel that you can fix or implement it yourself, please read on to learn h
- Add tests for your changes to `tests/`.
- Run tests and make sure all of them pass.
- Submit a pull request, referencing any issues it addresses.
-- Make sure to update the `CHANGELOG.md` file below the `Unreleased` heading.
We will review your pull request as soon as possible.
Thank you for contributing!
diff --git a/composer.json b/composer.json
index a6cc505c..087e9c77 100644
--- a/composer.json
+++ b/composer.json
@@ -26,6 +26,7 @@
"php": "^7.2||^8.0",
"jean85/pretty-package-versions": "^1.5 || ^2.0",
"sentry/sdk": "^3.3",
+ "sentry/sentry": "^3.12",
"symfony/cache-contracts": "^1.1||^2.4||^3.0",
"symfony/config": "^4.4.20||^5.0.11||^6.0",
"symfony/console": "^4.4.20||^5.0.11||^6.0",
@@ -97,7 +98,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "4.4.x-dev",
+ "dev-master": "4.6.x-dev",
"releases/3.2.x": "3.2.x-dev",
"releases/2.x": "2.x-dev",
"releases/1.x": "1.x-dev"
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 4dcdcbf9..a463d910 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -20,6 +20,11 @@ parameters:
count: 3
path: src/DependencyInjection/SentryExtension.php
+ -
+ message: "#^Cannot access offset 'before_send…' on mixed\\.$#"
+ count: 3
+ path: src/DependencyInjection/SentryExtension.php
+
-
message: "#^Cannot access offset 'class_serializers' on mixed\\.$#"
count: 3
@@ -52,7 +57,7 @@ parameters:
-
message: "#^Cannot access offset 'integrations' on mixed\\.$#"
- count: 3
+ count: 2
path: src/DependencyInjection/SentryExtension.php
-
@@ -77,7 +82,7 @@ parameters:
-
message: "#^Parameter \\#1 \\$id of class Symfony\\\\Component\\\\DependencyInjection\\\\Reference constructor expects string, mixed given\\.$#"
- count: 5
+ count: 6
path: src/DependencyInjection/SentryExtension.php
-
@@ -280,11 +285,6 @@ parameters:
count: 1
path: tests/DependencyInjection/SentryExtensionTest.php
- -
- message: "#^Cannot access offset 'integrations' on mixed\\.$#"
- count: 1
- path: tests/DependencyInjection/SentryExtensionTest.php
-
-
message: "#^Class Symfony\\\\Component\\\\Debug\\\\Exception\\\\FatalErrorException not found\\.$#"
count: 1
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index b1c1bdf3..7dab828c 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -37,6 +37,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->info('If this value is not provided, the SDK will try to read it from the SENTRY_DSN environment variable. If that variable also does not exist, the SDK will just not send any events.')
->end()
->booleanNode('register_error_listener')->defaultTrue()->end()
+ ->booleanNode('register_error_handler')->defaultTrue()->end()
->scalarNode('logger')
->info('The service ID of the PSR-3 logger used to log messages coming from the SDK client. Be aware that setting the same logger of the application may create a circular loop when an event fails to be sent.')
->defaultNull()
@@ -91,6 +92,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->scalarNode('server_name')->end()
->scalarNode('before_send')->end()
+ ->scalarNode('before_send_transaction')->end()
->arrayNode('tags')
->useAttributeAsKey('name')
->normalizeKeys(false)
diff --git a/src/DependencyInjection/SentryExtension.php b/src/DependencyInjection/SentryExtension.php
index f87458bc..5e559a16 100644
--- a/src/DependencyInjection/SentryExtension.php
+++ b/src/DependencyInjection/SentryExtension.php
@@ -19,6 +19,7 @@
use Sentry\SentryBundle\EventListener\TracingConsoleListener;
use Sentry\SentryBundle\EventListener\TracingRequestListener;
use Sentry\SentryBundle\EventListener\TracingSubRequestListener;
+use Sentry\SentryBundle\Integration\IntegrationConfigurator;
use Sentry\SentryBundle\SentryBundle;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\ConnectionConfigurator;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware;
@@ -101,6 +102,10 @@ private function registerConfiguration(ContainerBuilder $container, array $confi
$options['before_send'] = new Reference($options['before_send']);
}
+ if (isset($options['before_send_transaction'])) {
+ $options['before_send_transaction'] = new Reference($options['before_send_transaction']);
+ }
+
if (isset($options['before_breadcrumb'])) {
$options['before_breadcrumb'] = new Reference($options['before_breadcrumb']);
}
@@ -111,9 +116,10 @@ private function registerConfiguration(ContainerBuilder $container, array $confi
}, $options['class_serializers']);
}
- if (isset($options['integrations'])) {
- $options['integrations'] = $this->configureIntegrationsOption($options['integrations'], $config);
- }
+ $container->getDefinition(IntegrationConfigurator::class)
+ ->setArgument(0, $this->configureIntegrationsOption($options['integrations'], $config))
+ ->setArgument(1, $config['register_error_handler']);
+ $options['integrations'] = new Reference(IntegrationConfigurator::class);
$container
->register('sentry.client.options', Options::class)
diff --git a/src/EventListener/ConsoleListener.php b/src/EventListener/ConsoleListener.php
index 46c57e1a..2d39ec89 100644
--- a/src/EventListener/ConsoleListener.php
+++ b/src/EventListener/ConsoleListener.php
@@ -4,6 +4,9 @@
namespace Sentry\SentryBundle\EventListener;
+use Sentry\Event;
+use Sentry\EventHint;
+use Sentry\ExceptionMechanism;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
@@ -82,7 +85,12 @@ public function handleConsoleErrorEvent(ConsoleErrorEvent $event): void
$scope->setTag('console.command.exit_code', (string) $event->getExitCode());
if ($this->captureErrors) {
- $this->hub->captureException($event->getError());
+ $hint = EventHint::fromArray([
+ 'exception' => $event->getError(),
+ 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
+ ]);
+
+ $this->hub->captureEvent(Event::createEvent(), $hint);
}
});
}
diff --git a/src/EventListener/ErrorListener.php b/src/EventListener/ErrorListener.php
index 5ebf8fdd..e4180e37 100644
--- a/src/EventListener/ErrorListener.php
+++ b/src/EventListener/ErrorListener.php
@@ -4,6 +4,9 @@
namespace Sentry\SentryBundle\EventListener;
+use Sentry\Event;
+use Sentry\EventHint;
+use Sentry\ExceptionMechanism;
use Sentry\State\HubInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
@@ -34,6 +37,11 @@ public function __construct(HubInterface $hub)
*/
public function handleExceptionEvent(ExceptionEvent $event): void
{
- $this->hub->captureException($event->getThrowable());
+ $hint = EventHint::fromArray([
+ 'exception' => $event->getThrowable(),
+ 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
+ ]);
+
+ $this->hub->captureEvent(Event::createEvent(), $hint);
}
}
diff --git a/src/EventListener/MessengerListener.php b/src/EventListener/MessengerListener.php
index e2af8fc2..f90d5574 100644
--- a/src/EventListener/MessengerListener.php
+++ b/src/EventListener/MessengerListener.php
@@ -5,6 +5,8 @@
namespace Sentry\SentryBundle\EventListener;
use Sentry\Event;
+use Sentry\EventHint;
+use Sentry\ExceptionMechanism;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
@@ -62,13 +64,7 @@ public function handleWorkerMessageFailedEvent(WorkerMessageFailedEvent $event):
$scope->setTag('messenger.message_bus', $messageBusStamp->getBusName());
}
- if ($exception instanceof HandlerFailedException) {
- foreach ($exception->getNestedExceptions() as $nestedException) {
- $this->hub->captureException($nestedException);
- }
- } else {
- $this->hub->captureException($exception);
- }
+ $this->captureException($exception, $event->willRetry());
});
$this->flushClient();
@@ -86,6 +82,33 @@ public function handleWorkerMessageHandledEvent(WorkerMessageHandledEvent $event
$this->flushClient();
}
+ /**
+ * Creates Sentry events from the given exception.
+ *
+ * Unpacks multiple exceptions wrapped in a HandlerFailedException and notifies
+ * Sentry of each individual exception.
+ *
+ * If the message will be retried the exceptions will be marked as handled
+ * in Sentry.
+ */
+ private function captureException(\Throwable $exception, bool $willRetry): void
+ {
+ if ($exception instanceof HandlerFailedException) {
+ foreach ($exception->getNestedExceptions() as $nestedException) {
+ $this->captureException($nestedException, $willRetry);
+ }
+
+ return;
+ }
+
+ $hint = EventHint::fromArray([
+ 'exception' => $exception,
+ 'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, $willRetry),
+ ]);
+
+ $this->hub->captureEvent(Event::createEvent(), $hint);
+ }
+
private function flushClient(): void
{
$client = $this->hub->getClient();
diff --git a/src/Integration/IntegrationConfigurator.php b/src/Integration/IntegrationConfigurator.php
new file mode 100644
index 00000000..643b395f
--- /dev/null
+++ b/src/Integration/IntegrationConfigurator.php
@@ -0,0 +1,80 @@
+ true,
+ ExceptionListenerIntegration::class => true,
+ FatalErrorListenerIntegration::class => true,
+ ];
+
+ /**
+ * @var IntegrationInterface[]
+ */
+ private $userIntegrations;
+
+ /**
+ * @var bool
+ */
+ private $registerErrorHandler;
+
+ /**
+ * @param IntegrationInterface[] $userIntegrations
+ */
+ public function __construct(array $userIntegrations, bool $registerErrorHandler)
+ {
+ $this->userIntegrations = $userIntegrations;
+ $this->registerErrorHandler = $registerErrorHandler;
+ }
+
+ /**
+ * @see IntegrationRegistry::getIntegrationsToSetup()
+ *
+ * @param IntegrationInterface[] $defaultIntegrations
+ *
+ * @return IntegrationInterface[]
+ */
+ public function __invoke(array $defaultIntegrations): array
+ {
+ $integrations = [];
+
+ $userIntegrationsClasses = array_map('get_class', $this->userIntegrations);
+ $pickedIntegrationsClasses = [];
+
+ foreach ($defaultIntegrations as $defaultIntegration) {
+ $integrationClassName = \get_class($defaultIntegration);
+
+ if (!$this->registerErrorHandler && isset(self::ERROR_HANDLER_INTEGRATIONS[$integrationClassName])) {
+ continue;
+ }
+
+ if (!\in_array($integrationClassName, $userIntegrationsClasses, true) && !isset($pickedIntegrationsClasses[$integrationClassName])) {
+ $integrations[] = $defaultIntegration;
+ $pickedIntegrationsClasses[$integrationClassName] = true;
+ }
+ }
+
+ foreach ($this->userIntegrations as $userIntegration) {
+ $integrationClassName = \get_class($userIntegration);
+
+ if (!isset($pickedIntegrationsClasses[$integrationClassName])) {
+ $integrations[] = $userIntegration;
+ $pickedIntegrationsClasses[$integrationClassName] = true;
+ }
+ }
+
+ return $integrations;
+ }
+}
diff --git a/src/Resources/config/schema/sentry-1.0.xsd b/src/Resources/config/schema/sentry-1.0.xsd
index a140d46c..4a803ed2 100644
--- a/src/Resources/config/schema/sentry-1.0.xsd
+++ b/src/Resources/config/schema/sentry-1.0.xsd
@@ -15,6 +15,7 @@
+
@@ -44,6 +45,7 @@
+
diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml
index 41fe3633..328a05a3 100644
--- a/src/Resources/config/services.xml
+++ b/src/Resources/config/services.xml
@@ -121,6 +121,11 @@
+
+
+
+
+
diff --git a/src/Tracing/HttpClient/AbstractTraceableHttpClient.php b/src/Tracing/HttpClient/AbstractTraceableHttpClient.php
index f4a2a35e..11f2ffbf 100644
--- a/src/Tracing/HttpClient/AbstractTraceableHttpClient.php
+++ b/src/Tracing/HttpClient/AbstractTraceableHttpClient.php
@@ -52,6 +52,12 @@ public function request(string $method, string $url, array $options = []): Respo
$headers['sentry-trace'] = $parent->toTraceparent();
$uri = new Uri($url);
+ $partialUri = Uri::fromParts([
+ 'scheme' => $uri->getScheme(),
+ 'host' => $uri->getHost(),
+ 'port' => $uri->getPort(),
+ 'path' => $uri->getPath(),
+ ]);
// Check if the request destination is allow listed in the trace_propagation_targets option.
$client = $this->hub->getClient();
@@ -65,14 +71,16 @@ public function request(string $method, string $url, array $options = []): Respo
$options['headers'] = $headers;
- $formattedUri = $this->formatUri($uri);
-
$context = new SpanContext();
$context->setOp('http.client');
- $context->setDescription($method . ' ' . $formattedUri);
+ $context->setDescription($method . ' ' . (string) $partialUri);
$context->setTags([
'http.method' => $method,
- 'http.url' => $formattedUri,
+ 'http.url' => (string) $partialUri,
+ ]);
+ $context->setData([
+ 'http.query' => $uri->getQuery(),
+ 'http.fragment' => $uri->getFragment(),
]);
$span = $parent->startChild($context);
@@ -111,10 +119,4 @@ public function setLogger(LoggerInterface $logger): void
$this->client->setLogger($logger);
}
}
-
- private function formatUri(Uri $uri): string
- {
- // Instead of relying on Uri::__toString, we only use a sub set of the URI
- return Uri::composeComponents($uri->getScheme(), $uri->getHost(), $uri->getPath(), null, null);
- }
}
diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php
index 76aaeaf8..06727242 100644
--- a/tests/DependencyInjection/ConfigurationTest.php
+++ b/tests/DependencyInjection/ConfigurationTest.php
@@ -21,6 +21,7 @@ public function testProcessConfigurationWithDefaultConfiguration(): void
{
$expectedBundleDefaultConfig = [
'register_error_listener' => true,
+ 'register_error_handler' => true,
'logger' => null,
'transport_factory' => 'Sentry\\Transport\\TransportFactoryInterface',
'options' => [
diff --git a/tests/DependencyInjection/Fixtures/php/error_handler_disabled.php b/tests/DependencyInjection/Fixtures/php/error_handler_disabled.php
new file mode 100644
index 00000000..0f433a47
--- /dev/null
+++ b/tests/DependencyInjection/Fixtures/php/error_handler_disabled.php
@@ -0,0 +1,10 @@
+loadFromExtension('sentry', [
+ 'register_error_handler' => false,
+]);
diff --git a/tests/DependencyInjection/Fixtures/php/full.php b/tests/DependencyInjection/Fixtures/php/full.php
index cf52ebf9..52a5bc60 100644
--- a/tests/DependencyInjection/Fixtures/php/full.php
+++ b/tests/DependencyInjection/Fixtures/php/full.php
@@ -26,6 +26,7 @@
'release' => '4.0.x-dev',
'server_name' => 'localhost',
'before_send' => 'App\\Sentry\\BeforeSendCallback',
+ 'before_send_transaction' => 'App\\Sentry\\BeforeSendTransactionCallback',
'tags' => [
'context' => 'development',
],
diff --git a/tests/DependencyInjection/Fixtures/xml/error_handler_disabled.xml b/tests/DependencyInjection/Fixtures/xml/error_handler_disabled.xml
new file mode 100644
index 00000000..3050ee1f
--- /dev/null
+++ b/tests/DependencyInjection/Fixtures/xml/error_handler_disabled.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/tests/DependencyInjection/Fixtures/xml/full.xml b/tests/DependencyInjection/Fixtures/xml/full.xml
index bab488bb..fe2b8bc1 100644
--- a/tests/DependencyInjection/Fixtures/xml/full.xml
+++ b/tests/DependencyInjection/Fixtures/xml/full.xml
@@ -24,6 +24,7 @@
release="4.0.x-dev"
server-name="localhost"
before-send="App\Sentry\BeforeSendCallback"
+ before-send-transaction="App\Sentry\BeforeSendTransactionCallback"
error-types="E_ALL"
max-breadcrumbs="1"
before-breadcrumb="App\Sentry\BeforeBreadcrumbCallback"
diff --git a/tests/DependencyInjection/Fixtures/yml/error_handler_disabled.yml b/tests/DependencyInjection/Fixtures/yml/error_handler_disabled.yml
new file mode 100644
index 00000000..4bcbfd85
--- /dev/null
+++ b/tests/DependencyInjection/Fixtures/yml/error_handler_disabled.yml
@@ -0,0 +1,2 @@
+sentry:
+ register_error_handler: false
diff --git a/tests/DependencyInjection/Fixtures/yml/full.yml b/tests/DependencyInjection/Fixtures/yml/full.yml
index d0799679..f7fd7b05 100644
--- a/tests/DependencyInjection/Fixtures/yml/full.yml
+++ b/tests/DependencyInjection/Fixtures/yml/full.yml
@@ -22,6 +22,7 @@ sentry:
release: 4.0.x-dev
server_name: localhost
before_send: App\Sentry\BeforeSendCallback
+ before_send_transaction: App\Sentry\BeforeSendTransactionCallback
tags:
context: development
error_types: !php/const E_ALL
diff --git a/tests/DependencyInjection/SentryExtensionTest.php b/tests/DependencyInjection/SentryExtensionTest.php
index d2055e70..fb59da6a 100644
--- a/tests/DependencyInjection/SentryExtensionTest.php
+++ b/tests/DependencyInjection/SentryExtensionTest.php
@@ -19,6 +19,7 @@
use Sentry\SentryBundle\EventListener\TracingConsoleListener;
use Sentry\SentryBundle\EventListener\TracingRequestListener;
use Sentry\SentryBundle\EventListener\TracingSubRequestListener;
+use Sentry\SentryBundle\Integration\IntegrationConfigurator;
use Sentry\SentryBundle\SentryBundle;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\ConnectionConfigurator;
use Sentry\SentryBundle\Tracing\Doctrine\DBAL\TracingDriverMiddleware;
@@ -64,6 +65,18 @@ public function testErrorListenerIsRemovedWhenDisabled(): void
$this->assertFalse($container->hasDefinition(ErrorListener::class));
}
+ /**
+ * @testWith ["full", true]
+ * ["error_handler_disabled", false]
+ */
+ public function testIntegrationConfiguratorRegisterErrorHandlerArgument(string $fixtureFile, bool $expected): void
+ {
+ $container = $this->createContainerFromFixture($fixtureFile);
+ $definition = $container->getDefinition(IntegrationConfigurator::class);
+
+ $this->assertSame($expected, $definition->getArgument(1));
+ }
+
public function testConsoleCommandListener(): void
{
$container = $this->createContainerFromFixture('full');
@@ -185,17 +198,7 @@ public function testClientIsCreatedFromOptions(): void
$container = $this->createContainerFromFixture('full');
$optionsDefinition = $container->getDefinition('sentry.client.options');
$expectedOptions = [
- 'integrations' => [
- new Definition(IgnoreErrorsIntegration::class, [
- [
- 'ignore_exceptions' => [
- FatalError::class,
- FatalErrorException::class,
- ],
- ],
- ]),
- new Reference('App\\Sentry\\Integration\\FooIntegration'),
- ],
+ 'integrations' => new Reference(IntegrationConfigurator::class),
'default_integrations' => false,
'send_attempts' => 1,
'prefixes' => [$container->getParameter('kernel.project_dir')],
@@ -211,6 +214,7 @@ public function testClientIsCreatedFromOptions(): void
'release' => '4.0.x-dev',
'server_name' => 'localhost',
'before_send' => new Reference('App\\Sentry\\BeforeSendCallback'),
+ 'before_send_transaction' => new Reference('App\\Sentry\\BeforeSendTransactionCallback'),
'tags' => ['context' => 'development'],
'error_types' => \E_ALL,
'max_breadcrumbs' => 1,
@@ -233,6 +237,22 @@ public function testClientIsCreatedFromOptions(): void
$this->assertSame(Options::class, $optionsDefinition->getClass());
$this->assertEquals($expectedOptions, $optionsDefinition->getArgument(0));
+ $integrationConfiguratorDefinition = $container->getDefinition(IntegrationConfigurator::class);
+ $expectedIntegrations = [
+ new Definition(IgnoreErrorsIntegration::class, [
+ [
+ 'ignore_exceptions' => [
+ FatalError::class,
+ FatalErrorException::class,
+ ],
+ ],
+ ]),
+ new Reference('App\\Sentry\\Integration\\FooIntegration'),
+ ];
+
+ $this->assertSame(IntegrationConfigurator::class, $integrationConfiguratorDefinition->getClass());
+ $this->assertEquals($expectedIntegrations, $integrationConfiguratorDefinition->getArgument(0));
+
$clientDefinition = $container->findDefinition(ClientInterface::class);
$factory = $clientDefinition->getFactory();
@@ -281,7 +301,7 @@ public function testErrorTypesOptionIsParsedFromStringToIntegerValue(): void
public function testIgnoreErrorsIntegrationIsNotAddedTwiceIfAlreadyConfigured(): void
{
$container = $this->createContainerFromFixture('ignore_errors_integration_overridden');
- $integrations = $container->getDefinition('sentry.client.options')->getArgument(0)['integrations'];
+ $integrations = $container->getDefinition(IntegrationConfigurator::class)->getArgument(0);
$ignoreErrorsIntegrationsCount = 0;
foreach ($integrations as $integration) {
diff --git a/tests/EventListener/AbstractConsoleListenerTest.php b/tests/EventListener/AbstractConsoleListenerTest.php
index 9e124523..89fb94c5 100644
--- a/tests/EventListener/AbstractConsoleListenerTest.php
+++ b/tests/EventListener/AbstractConsoleListenerTest.php
@@ -7,6 +7,7 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Sentry\Event;
+use Sentry\EventHint;
use Sentry\SentryBundle\EventListener\ConsoleListener;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
@@ -113,8 +114,20 @@ public function testHandleConsoleErrorEvent(bool $captureErrors): void
});
$this->hub->expects($captureErrors ? $this->once() : $this->never())
- ->method('captureException')
- ->with($consoleEvent->getError());
+ ->method('captureEvent')
+ ->with(
+ $this->anything(),
+ $this->logicalAnd(
+ $this->isInstanceOf(EventHint::class),
+ $this->callback(function (EventHint $subject) use ($consoleEvent) {
+ self::assertSame($consoleEvent->getError(), $subject->exception);
+ self::assertNotNull($subject->mechanism);
+ self::assertFalse($subject->mechanism->isHandled());
+
+ return true;
+ })
+ )
+ );
$listener->handleConsoleErrorEvent($consoleEvent);
diff --git a/tests/EventListener/ErrorListenerTest.php b/tests/EventListener/ErrorListenerTest.php
index f97ff438..602f1d80 100644
--- a/tests/EventListener/ErrorListenerTest.php
+++ b/tests/EventListener/ErrorListenerTest.php
@@ -6,6 +6,7 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+use Sentry\EventHint;
use Sentry\SentryBundle\EventListener\ErrorListener;
use Sentry\State\HubInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -35,9 +36,23 @@ protected function setUp(): void
*/
public function testHandleExceptionEvent(ExceptionEvent $event): void
{
+ $expectedException = $event->getThrowable();
+
$this->hub->expects($this->once())
- ->method('captureException')
- ->with($event->getThrowable());
+ ->method('captureEvent')
+ ->with(
+ $this->anything(),
+ $this->logicalAnd(
+ $this->isInstanceOf(EventHint::class),
+ $this->callback(function (EventHint $subject) use ($expectedException) {
+ self::assertSame($expectedException, $subject->exception);
+ self::assertNotNull($subject->mechanism);
+ self::assertFalse($subject->mechanism->isHandled());
+
+ return true;
+ })
+ )
+ );
$this->listener->handleExceptionEvent($event);
}
diff --git a/tests/EventListener/MessengerListenerTest.php b/tests/EventListener/MessengerListenerTest.php
index af7b8462..0404edad 100644
--- a/tests/EventListener/MessengerListenerTest.php
+++ b/tests/EventListener/MessengerListenerTest.php
@@ -8,6 +8,7 @@
use PHPUnit\Framework\TestCase;
use Sentry\ClientInterface;
use Sentry\Event;
+use Sentry\EventHint;
use Sentry\SentryBundle\EventListener\MessengerListener;
use Sentry\State\HubInterface;
use Sentry\State\Scope;
@@ -42,7 +43,7 @@ protected function setUp(): void
* @param \Throwable[] $exceptions
* @param array $expectedTags
*/
- public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMessageFailedEvent $event, array $expectedTags): void
+ public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMessageFailedEvent $event, array $expectedTags, bool $expectedIsHandled): void
{
if (!$this->supportsMessenger()) {
$this->markTestSkipped('Messenger not supported in this environment.');
@@ -57,9 +58,21 @@ public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMess
});
$this->hub->expects($this->exactly(\count($exceptions)))
- ->method('captureException')
- ->withConsecutive(...array_map(static function (\Throwable $exception): array {
- return [$exception];
+ ->method('captureEvent')
+ ->withConsecutive(...array_map(function (\Throwable $expectedException) use ($expectedIsHandled): array {
+ return [
+ $this->anything(),
+ $this->logicalAnd(
+ $this->isInstanceOf(EventHint::class),
+ $this->callback(function (EventHint $subject) use ($expectedException, $expectedIsHandled) {
+ self::assertSame($expectedException, $subject->exception);
+ self::assertNotNull($subject->mechanism);
+ self::assertSame($expectedIsHandled, $subject->mechanism->isHandled());
+
+ return true;
+ })
+ ),
+ ];
}, $exceptions));
$this->hub->expects($this->once())
@@ -97,6 +110,17 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
'messenger.receiver_name' => 'receiver',
'messenger.message_class' => \get_class($envelope->getMessage()),
],
+ false,
+ ];
+
+ yield 'envelope.throwable INSTANCEOF HandlerFailedException - RETRYING' => [
+ $exceptions,
+ $this->getMessageFailedEvent($envelope, 'receiver', new HandlerFailedException($envelope, $exceptions), true),
+ [
+ 'messenger.receiver_name' => 'receiver',
+ 'messenger.message_class' => \get_class($envelope->getMessage()),
+ ],
+ true,
];
yield 'envelope.throwable INSTANCEOF Exception' => [
@@ -106,6 +130,17 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
'messenger.receiver_name' => 'receiver',
'messenger.message_class' => \get_class($envelope->getMessage()),
],
+ false,
+ ];
+
+ yield 'envelope.throwable INSTANCEOF Exception - RETRYING' => [
+ [$exceptions[0]],
+ $this->getMessageFailedEvent($envelope, 'receiver', $exceptions[0], true),
+ [
+ 'messenger.receiver_name' => 'receiver',
+ 'messenger.message_class' => \get_class($envelope->getMessage()),
+ ],
+ true,
];
$envelope = new Envelope((object) [], [new BusNameStamp('bus.foo')]);
@@ -118,6 +153,7 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
'messenger.message_class' => \get_class($envelope->getMessage()),
'messenger.message_bus' => 'bus.foo',
],
+ false,
];
}
diff --git a/tests/Integration/IntegrationConfiguratorTest.php b/tests/Integration/IntegrationConfiguratorTest.php
new file mode 100644
index 00000000..44206b5c
--- /dev/null
+++ b/tests/Integration/IntegrationConfiguratorTest.php
@@ -0,0 +1,176 @@
+assertSame($expectedIntegrations, $integrationConfigurator($defaultIntegrations));
+ }
+
+ /**
+ * @return iterable
+ */
+ public function integrationsDataProvider(): iterable
+ {
+ $exceptionListenerIntegration = new ExceptionListenerIntegration();
+ $errorListenerIntegration = new ErrorListenerIntegration();
+ $fatalErrorListenerIntegration = new FatalErrorListenerIntegration();
+ $requestIntegration = new RequestIntegration();
+ $transactionIntegration = new TransactionIntegration();
+ $frameContextifierIntegration = new FrameContextifierIntegration();
+ $environmentIntegration = new EnvironmentIntegration();
+ $modulesIntegration = new ModulesIntegration();
+
+ $userIntegration1 = new class() implements IntegrationInterface {
+ public function setupOnce(): void
+ {
+ }
+ };
+ $userRequestIntegration = new RequestIntegration();
+
+ yield 'Default integrations, register error handler true and no user integrations' => [
+ [],
+ true,
+ [
+ $exceptionListenerIntegration,
+ $errorListenerIntegration,
+ $fatalErrorListenerIntegration,
+ $requestIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ ],
+ [
+ $exceptionListenerIntegration,
+ $errorListenerIntegration,
+ $fatalErrorListenerIntegration,
+ $requestIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ ],
+ ];
+
+ yield 'Default integrations, register error handler false and no user integrations' => [
+ [],
+ false,
+ [
+ $exceptionListenerIntegration,
+ $errorListenerIntegration,
+ $fatalErrorListenerIntegration,
+ $requestIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ ],
+ [
+ $requestIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ ],
+ ];
+
+ yield 'Default integrations, register error handler true and some user integrations, one of which is also a default integration' => [
+ [
+ $userIntegration1,
+ $userRequestIntegration,
+ ],
+ true,
+ [
+ $exceptionListenerIntegration,
+ $errorListenerIntegration,
+ $fatalErrorListenerIntegration,
+ $requestIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ ],
+ [
+ $exceptionListenerIntegration,
+ $errorListenerIntegration,
+ $fatalErrorListenerIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ $userIntegration1,
+ $userRequestIntegration,
+ ],
+ ];
+
+ yield 'Default integrations, register error handler false and some user integrations, one of which is also a default integration' => [
+ [
+ $userIntegration1,
+ $userRequestIntegration,
+ ],
+ false,
+ [
+ $exceptionListenerIntegration,
+ $errorListenerIntegration,
+ $fatalErrorListenerIntegration,
+ $requestIntegration,
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ ],
+ [
+ $transactionIntegration,
+ $frameContextifierIntegration,
+ $environmentIntegration,
+ $modulesIntegration,
+ $userIntegration1,
+ $userRequestIntegration,
+ ],
+ ];
+
+ yield 'No default integrations and some user integrations are repeated twice' => [
+ [
+ $userIntegration1,
+ $userRequestIntegration,
+ $userIntegration1,
+ ],
+ true,
+ [],
+ [
+ $userIntegration1,
+ $userRequestIntegration,
+ ],
+ ];
+ }
+}
diff --git a/tests/Tracing/HttpClient/TraceableHttpClientTest.php b/tests/Tracing/HttpClient/TraceableHttpClientTest.php
index 06636d26..0a814ded 100644
--- a/tests/Tracing/HttpClient/TraceableHttpClientTest.php
+++ b/tests/Tracing/HttpClient/TraceableHttpClientTest.php
@@ -78,12 +78,17 @@ public function testRequest(): void
'http.method' => 'GET',
'http.url' => 'https://www.example.com/test-page',
];
+ $expectedData = [
+ 'http.query' => 'foo=bar',
+ 'http.fragment' => 'baz',
+ ];
$this->assertCount(2, $spans);
$this->assertNull($spans[1]->getEndTimestamp());
$this->assertSame('http.client', $spans[1]->getOp());
$this->assertSame('GET https://www.example.com/test-page', $spans[1]->getDescription());
$this->assertSame($expectedTags, $spans[1]->getTags());
+ $this->assertSame($expectedData, $spans[1]->getData());
}
public function testRequestDoesNotContainBaggageHeader(): void