Skip to content

Add attribute/tag for command status tracking #922

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/Attribute/SentryMonitorCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS)]
class SentryMonitorCommand
{
/**
* @var string
*/
private $slug;

public function __construct(string $slug)
{
$this->slug = $slug;
}

public function getSlug(): string
{
return $this->slug;
}
}
34 changes: 34 additions & 0 deletions src/DependencyInjection/Compiler/CronMonitorPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\DependencyInjection\Compiler;

use Sentry\SentryBundle\EventListener\CronMonitorListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class CronMonitorPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

public function process(ContainerBuilder $container): void
{
if (!$container->getParameter('sentry.cron.enabled')) {
return;
}

$commands = $this->findAndSortTaggedServices('sentry.monitor_command', $container);

$commandSlugMapping = [];
foreach ($commands as $reference) {
$id = $reference->__toString();
foreach ($container->getDefinition($id)->getTag('sentry.monitor_command') as $attributes) {
$commandSlugMapping[$id] = $attributes['slug'];
}
}

$container->getDefinition(CronMonitorListener::class)->setArgument(1, $commandSlugMapping);
}
}
1 change: 1 addition & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->booleanNode('register_error_listener')->defaultTrue()->end()
->booleanNode('register_error_handler')->defaultTrue()->end()
->booleanNode('register_cron_monitor')->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()
Expand Down
30 changes: 30 additions & 0 deletions src/DependencyInjection/SentryExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
use Sentry\Integration\RequestFetcherInterface;
use Sentry\Integration\RequestIntegration;
use Sentry\Options;
use Sentry\SentryBundle\Attribute\SentryMonitorCommand;
use Sentry\SentryBundle\EventListener\ConsoleListener;
use Sentry\SentryBundle\EventListener\CronMonitorListener;
use Sentry\SentryBundle\EventListener\ErrorListener;
use Sentry\SentryBundle\EventListener\LoginListener;
use Sentry\SentryBundle\EventListener\MessengerListener;
Expand All @@ -29,6 +31,7 @@
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader;
Expand Down Expand Up @@ -77,6 +80,7 @@ protected function loadInternal(array $mergedConfig, ContainerBuilder $container
$this->registerTwigTracingConfiguration($container, $mergedConfig['tracing']);
$this->registerCacheTracingConfiguration($container, $mergedConfig['tracing']);
$this->registerHttpClientTracingConfiguration($container, $mergedConfig['tracing']);
$this->registerCronMonitoringConfiguration($container, $mergedConfig);

if (!interface_exists(TokenStorageInterface::class)) {
$container->removeDefinition(LoginListener::class);
Expand Down Expand Up @@ -285,6 +289,32 @@ private function registerHttpClientTracingConfiguration(ContainerBuilder $contai
$container->setParameter('sentry.tracing.http_client.enabled', $isConfigEnabled);
}

/**
* @param array<string, mixed> $config
*/
private function registerCronMonitoringConfiguration(ContainerBuilder $container, array $config): void
{
$container->setParameter('sentry.cron.enabled', (bool) $config['register_cron_monitor']);

if (!$config['register_cron_monitor']) {
$container->removeDefinition(CronMonitorListener::class);

return;
}

if (\PHP_VERSION > 8.1) {
$container->registerAttributeForAutoconfiguration(
SentryMonitorCommand::class,
static function (
ChildDefinition $definition,
SentryMonitorCommand $attribute
) {
$definition->addTag('sentry.monitor_command', ['slug' => $attribute->getSlug()]);
}
);
}
}

/**
* @param string[] $integrations
* @param array<string, mixed> $config
Expand Down
104 changes: 104 additions & 0 deletions src/EventListener/CronMonitorListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\EventListener;

use Sentry\CheckInStatus;
use Sentry\State\HubInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

class CronMonitorListener
{
/**
* @var HubInterface
*/
private $hub;

/**
* @var array<string>
*/
private $registeredCommands;

/**
* @var string|null
*/
private $checkinId;

/**
* @param HubInterface $hub
* @param array<string> $registeredCommands
*/
public function __construct(HubInterface $hub, array $registeredCommands = [])
{
$this->hub = $hub;
$this->registeredCommands = $registeredCommands;
}

public function handleConsoleCommandEvent(ConsoleCommandEvent $event): void
{
$command = $event->getCommand();

if (false === $this->isValid($command)) {
return;
}

$this->checkinId = $this->hub->captureCheckIn(
$this->registeredCommands[$this->getCommandIndex($command)],
CheckInStatus::inProgress()
);
}

public function handleConsoleTerminateEvent(ConsoleTerminateEvent $event): void
{
$command = $event->getCommand();

if (false === $this->isValid($command)) {
return;
}

$this->hub->captureCheckIn(
$this->registeredCommands[$this->getCommandIndex($command)],
Command::SUCCESS === $event->getExitCode()
? CheckInStatus::ok()
: CheckInStatus::error(),
null,
null,
$this->checkinId
);
}

public function handleConsoleErrorEvent(ConsoleErrorEvent $event): void
{
$command = $event->getCommand();

if (false === $this->isValid($command)) {
return;
}

$this->hub->captureCheckIn(
$this->registeredCommands[$this->getCommandIndex($command)],
CheckInStatus::error(),
null,
null,
$this->checkinId
);
}

private function isValid(?Command $command): bool
{
return $command instanceof Command && isset($this->registeredCommands[$this->getCommandIndex($command)]);
}

private function getCommandIndex(?Command $command): string
{
if (null === $command) {
return '';
}

return \get_class($command);
}
}
1 change: 1 addition & 0 deletions src/Resources/config/schema/sentry-1.0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

<xsd:attribute name="register-error-listener" type="xsd:boolean" />
<xsd:attribute name="register-error-handler" type="xsd:boolean" />
<xsd:attribute name="register-cron-monitor" type="xsd:boolean" />
<xsd:attribute name="transport-factory" type="xsd:string" />
<xsd:attribute name="dsn" type="xsd:string" />
<xsd:attribute name="logger" type="xsd:string" />
Expand Down
8 changes: 8 additions & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
<tag name="kernel.event_listener" event="console.error" method="handleConsoleErrorEvent" priority="-64" />
</service>

<service id="Sentry\SentryBundle\EventListener\CronMonitorListener" class="Sentry\SentryBundle\EventListener\CronMonitorListener">
<argument type="service" id="Sentry\State\HubInterface" />

<tag name="kernel.event_listener" event="console.command" method="handleConsoleCommandEvent" priority="128" />
<tag name="kernel.event_listener" event="console.terminate" method="handleConsoleTerminateEvent" priority="-64" />
<tag name="kernel.event_listener" event="console.error" method="handleConsoleErrorEvent" priority="-64" />
</service>

<service id="Sentry\SentryBundle\EventListener\ErrorListener" class="Sentry\SentryBundle\EventListener\ErrorListener">
<argument type="service" id="Sentry\State\HubInterface" />

Expand Down
2 changes: 2 additions & 0 deletions src/SentryBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Sentry\SentryBundle\DependencyInjection\Compiler\AddLoginListenerTagPass;
use Sentry\SentryBundle\DependencyInjection\Compiler\CacheTracingPass;
use Sentry\SentryBundle\DependencyInjection\Compiler\CronMonitorPass;
use Sentry\SentryBundle\DependencyInjection\Compiler\DbalTracingPass;
use Sentry\SentryBundle\DependencyInjection\Compiler\HttpClientTracingPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
Expand All @@ -26,5 +27,6 @@ public function build(ContainerBuilder $container): void
$container->addCompilerPass(new CacheTracingPass());
$container->addCompilerPass(new HttpClientTracingPass());
$container->addCompilerPass(new AddLoginListenerTagPass());
$container->addCompilerPass(new CronMonitorPass());
}
}
61 changes: 61 additions & 0 deletions tests/DependencyInjection/Compiler/CronMonitorPassTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Sentry\SentryBundle\Tests\DependencyInjection\Compiler;

use PHPUnit\Framework\TestCase;
use Sentry\SentryBundle\Command\SentryTestCommand;
use Sentry\SentryBundle\DependencyInjection\Compiler\CronMonitorPass;
use Sentry\SentryBundle\EventListener\CronMonitorListener;
use Sentry\State\HubInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

class CronMonitorPassTest extends TestCase
{
public function testProcess(): void
{
$container = $this->createContainerBuilder(true);

$container->setDefinition(
SentryTestCommand::class,
(new Definition(SentryTestCommand::class))
->setPublic(false)
->addTag('console.command')
->addTag('sentry.monitor_command', ['slug' => 'test-command'])
);

$container->compile();

$insertedCronMonitorListenerArgument = $container->getDefinition(CronMonitorListener::class)->getArgument(1);

$this->assertIsArray($insertedCronMonitorListenerArgument);
$this->assertArrayHasKey(SentryTestCommand::class, $insertedCronMonitorListenerArgument);
$this->assertEquals('test-command', $insertedCronMonitorListenerArgument[SentryTestCommand::class]);
}

public function testProcessDoesNothingIfConditionsForEnablingCronIsFalse(): void
{
$container = $this->createContainerBuilder(false);
$container->compile();

$this->assertFalse($container->getDefinition(CronMonitorListener::class)->getArgument(1));
}

private function createContainerBuilder(bool $isCronActive): ContainerBuilder
{
$container = new ContainerBuilder();
$container->addCompilerPass(new CronMonitorPass());
$container->setParameter('sentry.cron.enabled', $isCronActive);

$cronMonitorListenerMock = $this->createMock(CronMonitorListener::class);

$container->setDefinition(CronMonitorListener::class, (new Definition(\get_class($cronMonitorListenerMock)))
->setPublic(true))
->setArgument(0, HubInterface::class)
->setArgument(1, false);

return $container;
}
}
1 change: 1 addition & 0 deletions tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function testProcessConfigurationWithDefaultConfiguration(): void
$expectedBundleDefaultConfig = [
'register_error_listener' => true,
'register_error_handler' => true,
'register_cron_monitor' => true,
'logger' => null,
'options' => [
'integrations' => [],
Expand Down
10 changes: 10 additions & 0 deletions tests/DependencyInjection/Fixtures/php/cron_monitor_disabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\ContainerBuilder;

/** @var ContainerBuilder $container */
$container->loadFromExtension('sentry', [
'register_cron_monitor' => false,
]);
10 changes: 10 additions & 0 deletions tests/DependencyInjection/Fixtures/php/cron_monitor_enabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\ContainerBuilder;

/** @var ContainerBuilder $container */
$container->loadFromExtension('sentry', [
'register_cron_monitor' => true,
]);
10 changes: 10 additions & 0 deletions tests/DependencyInjection/Fixtures/xml/cron_monitor_disabled.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sentry="https://sentry.io/schema/dic/sentry-symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
https://sentry.io/schema/dic/sentry-symfony https://sentry.io/schema/dic/sentry-symfony/sentry-1.0.xsd">

<sentry:config register-cron-monitor="false" />
</container>
10 changes: 10 additions & 0 deletions tests/DependencyInjection/Fixtures/xml/cron_monitor_enabled.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sentry="https://sentry.io/schema/dic/sentry-symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
https://sentry.io/schema/dic/sentry-symfony https://sentry.io/schema/dic/sentry-symfony/sentry-1.0.xsd">

<sentry:config register-cron-monitor="true" />
</container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sentry:
register_cron_monitor: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sentry:
register_cron_monitor: true
Loading
Loading