Skip to content

Commit

Permalink
Add Sentry BreadcrumbHandler support
Browse files Browse the repository at this point in the history
  • Loading branch information
HypeMC committed Aug 11, 2024
1 parent ed0e4a2 commit 10c35d5
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 16 deletions.
14 changes: 11 additions & 3 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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']); })
Expand Down Expand Up @@ -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')
Expand Down
29 changes: 24 additions & 5 deletions DependencyInjection/MonologExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -47,6 +48,8 @@ class MonologExtension extends Extension
{
private $nestedHandlers = [];

private $sentryBreadcrumbHandlers = [];

private $swiftMailerHandlers = [];

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'];
Expand Down Expand Up @@ -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'];
Expand Down Expand Up @@ -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',
Expand Down
65 changes: 57 additions & 8 deletions Tests/DependencyInjection/MonologExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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:[email protected]: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';
Expand Down

0 comments on commit 10c35d5

Please sign in to comment.