From 10c35d509489304aee2b937d19d2c0b8acb4a5bc Mon Sep 17 00:00:00 2001 From: HypeMC Date: Fri, 9 Aug 2024 13:46:44 +0200 Subject: [PATCH] Add Sentry BreadcrumbHandler support --- DependencyInjection/Configuration.php | 14 +++- DependencyInjection/MonologExtension.php | 29 +++++++-- .../MonologExtensionTest.php | 65 ++++++++++++++++--- 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 255eba47..e9d85f83 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -236,6 +236,9 @@ * - hub_id: Sentry hub custom service id (optional) * - [fill_extra_context]: bool, defaults to false * + * - sentry_breadcrumb: + * - sentry_handler: the sentry handler's name + * * - newrelic: * - [level]: level name or int value, defaults to DEBUG * - [bubble]: bool, defaults to true @@ -534,7 +537,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('max_level')->defaultValue('EMERGENCY')->end() // filter ->scalarNode('buffer_size')->defaultValue(0)->end() // fingers_crossed and buffer ->booleanNode('flush_on_overflow')->defaultFalse()->end() // buffer - ->scalarNode('handler')->end() // fingers_crossed and buffer + ->scalarNode('handler')->end() // fingers_crossed, buffer, filter, deduplication, sampling ->scalarNode('url')->end() // cube ->scalarNode('exchange')->end() // amqp ->scalarNode('exchange_name')->defaultValue('log')->end() // amqp @@ -584,6 +587,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('persistent')->end() // socket_handler ->scalarNode('dsn')->end() // raven_handler, sentry_handler ->scalarNode('hub_id')->defaultNull()->end() // sentry_handler + ->scalarNode('sentry_handler')->defaultNull()->end() // sentry_breadcrumb ->scalarNode('client_id')->defaultNull()->end() // raven_handler, sentry_handler ->scalarNode('auto_log_stacks')->defaultFalse()->end() // raven_handler ->scalarNode('release')->defaultNull()->end() // raven_handler, sentry_handler @@ -658,8 +662,8 @@ public function getConfigTreeBuilder(): TreeBuilder ->thenInvalid('Service handlers can not have a formatter configured in the bundle, you must reconfigure the service itself instead') ->end() ->validate() - ->ifTrue(function ($v) { return ('fingers_crossed' === $v['type'] || 'buffer' === $v['type'] || 'filter' === $v['type'] || 'sampling' === $v['type']) && empty($v['handler']); }) - ->thenInvalid('The handler has to be specified to use a FingersCrossedHandler or BufferHandler or FilterHandler or SamplingHandler') + ->ifTrue(function ($v) { return ('fingers_crossed' === $v['type'] || 'buffer' === $v['type'] || 'filter' === $v['type'] || 'deduplication' === $v['type'] || 'sampling' === $v['type']) && empty($v['handler']); }) + ->thenInvalid('The handler has to be specified to use a FingersCrossedHandler, BufferHandler, FilterHandler, DeduplicationHandler or SamplingHandler') ->end() ->validate() ->ifTrue(function ($v) { return 'fingers_crossed' === $v['type'] && !empty($v['excluded_404s']) && !empty($v['activation_strategy']); }) @@ -725,6 +729,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->ifTrue(function ($v) { return 'sentry' === $v['type'] && null !== $v['hub_id'] && null !== $v['client_id']; }) ->thenInvalid('You can not use both a hub_id and a client_id in a Sentry handler') ->end() + ->validate() + ->ifTrue(function ($v) { return 'sentry_breadcrumb' === $v['type'] && empty($v['sentry_handler']); }) + ->thenInvalid('The sentry_handler has to be specified to use a Sentry BreadcrumbHandler') + ->end() ->validate() ->ifTrue(function ($v) { return 'hipchat' === $v['type'] && (empty($v['token']) || empty($v['room'])); }) ->thenInvalid('The token and room have to be specified to use a HipChatHandler') diff --git a/DependencyInjection/MonologExtension.php b/DependencyInjection/MonologExtension.php index 1d47a984..fdf2206b 100644 --- a/DependencyInjection/MonologExtension.php +++ b/DependencyInjection/MonologExtension.php @@ -25,6 +25,7 @@ use Symfony\Bridge\Monolog\Processor\TokenProcessor; use Symfony\Bridge\Monolog\Processor\WebProcessor; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -47,6 +48,8 @@ class MonologExtension extends Extension { private $nestedHandlers = []; + private $sentryBreadcrumbHandlers = []; + private $swiftMailerHandlers = []; /** @@ -79,6 +82,11 @@ public function load(array $configs, ContainerBuilder $container) ]; } + foreach ($this->sentryBreadcrumbHandlers as $sentryBreadcrumbHandlerId => $sentryHandlerId) { + $hubId = (string) $container->getDefinition($sentryHandlerId)->getArgument(0); + $container->getDefinition($sentryBreadcrumbHandlerId)->replaceArgument(0, new Reference($hubId)); + } + $container->setParameter( 'monolog.swift_mailer.handlers', $this->swiftMailerHandlers @@ -724,7 +732,7 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler case 'sentry': if (null !== $handler['hub_id']) { - $hub = new Reference($handler['hub_id']); + $hubId = $handler['hub_id']; } else { if (null !== $handler['client_id']) { $clientId = $handler['client_id']; @@ -755,24 +763,34 @@ private function buildHandler(ContainerBuilder $container, $name, array $handler } } - $hub = new Definition( + $hubId = \sprintf('monolog.handler.%s.hub', $name); + $hub = $container->setDefinition($hubId, new Definition( 'Sentry\\State\\Hub', [new Reference($clientId)] - ); - $container->setDefinition(\sprintf('monolog.handler.%s.hub', $name), $hub); + )); // can't set the hub to the current hub, getting into a recursion otherwise... // $hub->addMethodCall('setCurrent', array($hub)); } $definition->setArguments([ - $hub, + new Reference($hubId), $handler['level'], $handler['bubble'], $handler['fill_extra_context'], ]); break; + case 'sentry_breadcrumb': + $this->sentryBreadcrumbHandlers[$handlerId] = $this->getHandlerId($handler['sentry_handler']); + + $definition->setArguments([ + new AbstractArgument('Sentry hub id'), + $handler['level'], + $handler['bubble'], + ]); + break; + case 'raven': if (null !== $handler['client_id']) { $clientId = $handler['client_id']; @@ -977,6 +995,7 @@ private function getHandlerClassByType($handlerType) 'pushover' => 'Monolog\Handler\PushoverHandler', 'raven' => 'Monolog\Handler\RavenHandler', 'sentry' => 'Sentry\Monolog\Handler', + 'sentry_breadcrumb' => 'Sentry\Monolog\BreadcrumbHandler', 'newrelic' => 'Monolog\Handler\NewRelicHandler', 'hipchat' => 'Monolog\Handler\HipChatHandler', 'slack' => 'Monolog\Handler\SlackHandler', diff --git a/Tests/DependencyInjection/MonologExtensionTest.php b/Tests/DependencyInjection/MonologExtensionTest.php index a0c82005..9cb6c60b 100644 --- a/Tests/DependencyInjection/MonologExtensionTest.php +++ b/Tests/DependencyInjection/MonologExtensionTest.php @@ -371,12 +371,10 @@ public function testRavenHandlerWhenAClientIsSpecified() public function testSentryHandlerWhenConfigurationIsWrong() { - try { - $this->getContainer([['handlers' => ['sentry' => ['type' => 'sentry']]]]); - $this->fail(); - } catch (InvalidConfigurationException $e) { - $this->assertStringContainsString('DSN', $e->getMessage()); - } + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The DSN has to be specified to use Sentry\'s handler'); + + $this->getContainer([['handlers' => ['sentry' => ['type' => 'sentry']]]]); } public function testSentryHandlerWhenADSNIsSpecified() @@ -426,7 +424,10 @@ public function testSentryHandlerWhenADSNAndAClientAreSpecified() $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', [new Reference('monolog.handler.sentry')]); $handler = $container->getDefinition('monolog.handler.sentry'); - $this->assertDICConstructorArguments($handler->getArguments()[0], [new Reference('sentry.client')]); + $this->assertDICConstructorArguments($handler, [new Reference('monolog.handler.sentry.hub'), 'DEBUG', true, false]); + + $hub = $container->getDefinition($handler->getArguments()[0]); + $this->assertDICConstructorArguments($hub, [new Reference('sentry.client')]); } public function testSentryHandlerWhenAClientIsSpecified() @@ -452,7 +453,10 @@ public function testSentryHandlerWhenAClientIsSpecified() $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', [new Reference('monolog.handler.sentry')]); $handler = $container->getDefinition('monolog.handler.sentry'); - $this->assertDICConstructorArguments($handler->getArguments()[0], [new Reference('sentry.client')]); + $this->assertDICConstructorArguments($handler, [new Reference('monolog.handler.sentry.hub'), 'DEBUG', true, false]); + + $hub = $container->getDefinition($handler->getArguments()[0]); + $this->assertDICConstructorArguments($hub, [new Reference('sentry.client')]); } public function testSentryHandlerWhenAHubIsSpecified() @@ -501,6 +505,51 @@ public function testSentryHandlerWhenAHubAndAClientAreSpecified() ); } + public function testSentryBreadcrumbHandlerWhenConfigurationIsWrong() + { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage('The sentry_handler has to be specified to use a Sentry BreadcrumbHandler'); + + $this->getContainer([['handlers' => ['sentry_breadcrumb' => ['type' => 'sentry_breadcrumb']]]]); + } + + /** + * @testWith [{"dsn": "http://a:b@c.com:9000/1"}, "monolog.handler.sentry.hub"] + * [{"hub_id": "sentry.hub"}, "sentry.hub"] + */ + public function testSentryBreadcrumbHandlerWhenConfigurationIsOk(array $config, string $hubId) + { + $container = $this->getContainer( + [ + [ + 'handlers' => [ + 'sentry_breadcrumb' => ['type' => 'sentry_breadcrumb', 'sentry_handler' => 'sentry'], + 'sentry' => ['type' => 'sentry'] + $config, + ], + ], + ], + [ + 'sentry.hub' => new Definition(\Sentry\State\HubInterface::class), + ] + ); + + $this->assertTrue($container->hasDefinition('monolog.logger')); + $this->assertTrue($container->hasDefinition('monolog.handler.sentry')); + $this->assertTrue($container->hasDefinition('monolog.handler.sentry_breadcrumb')); + + $logger = $container->getDefinition('monolog.logger'); + $this->assertDICDefinitionMethodCallAt(0, $logger, 'useMicrosecondTimestamps', ['%monolog.use_microseconds%']); + $this->assertDICDefinitionMethodCallAt(1, $logger, 'pushHandler', [new Reference('monolog.handler.sentry')]); + $this->assertDICDefinitionMethodCallAt(2, $logger, 'pushHandler', [new Reference('monolog.handler.sentry_breadcrumb')]); + + $handler = $container->getDefinition('monolog.handler.sentry_breadcrumb'); + $this->assertDICDefinitionClass($handler, 'Sentry\Monolog\BreadcrumbHandler'); + $this->assertDICConstructorArguments($handler, [new Reference($hubId), 'DEBUG', true]); + + $sentry = $container->getDefinition('monolog.handler.sentry'); + $this->assertSame((string) $sentry->getArgument(0), (string) $handler->getArgument(0)); + } + public function testLogglyHandler() { $token = '026308d8-2b63-4225-8fe9-e01294b6e472';