Skip to content

Commit

Permalink
feat: add scheduled task reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanpoensgen committed Mar 6, 2024
1 parent 5923d08 commit a03d361
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 2 deletions.
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
}
},
"require-dev": {
"phpstan/phpstan": "^1.10"
"phpstan/phpstan": "^1.10",
"phpstan/phpstan-symfony": "^1.3",
"phpstan/extension-installer": "^1.3"
},
"config": {
"allow-plugins": {
"php-http/discovery": false,
"symfony/runtime": false
"symfony/runtime": false,
"phpstan/extension-installer": true
}
}
}
26 changes: 26 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);


namespace Frosh\SentryBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;

class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('frosh_sentry');
$rootNode = $treeBuilder->getRootNode();

$rootNode
->children()
->booleanNode('report_scheduled_tasks')->defaultFalse()->end()
->end();

return $treeBuilder;
}
}
43 changes: 43 additions & 0 deletions src/DependencyInjection/FroshSentryExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Frosh\SentryBundle\DependencyInjection;

use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

class FroshSentryExtension extends Extension
{
/**
* @param array<mixed> $configs
*/
public function load(array $configs, ContainerBuilder $container): void
{
$config = $this->processConfiguration($this->getConfiguration($configs, $container), $configs);
$this->addConfig($container, $this->getAlias(), $config);
}

/**
* @param array<mixed> $config
*/
public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface
{
return new Configuration();
}

/**
* @param array<array<string>|bool|float|int|string|null> $options
*/
private function addConfig(ContainerBuilder $container, string $alias, array $options): void
{
foreach ($options as $key => $option) {
$container->setParameter($alias . '.' . $key, $option);

if (\is_array($option)) {
$this->addConfig($container, $alias . '.' . $key, $option);
}
}
}
}
14 changes: 14 additions & 0 deletions src/ShopwareSentryBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

namespace Frosh\SentryBundle;

use Frosh\SentryBundle\DependencyInjection\FroshSentryExtension;
use Frosh\SentryBundle\Instrumentation\SentryProfiler;
use Frosh\SentryBundle\Integration\UseShopwareExceptionIgnores;
use Frosh\SentryBundle\Listener\FixRequestUrlListener;
use Frosh\SentryBundle\Listener\SalesChannelContextListener;
use Frosh\SentryBundle\Subscriber\ScheduledTaskSubscriber;
use Shopware\Core\System\SalesChannel\Event\SalesChannelContextCreatedEvent;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Event\RequestEvent;
Expand Down Expand Up @@ -35,6 +38,17 @@ public function build(ContainerBuilder $container): void
->register(UseShopwareExceptionIgnores::class)
->addArgument('%frosh_sentry.exclude_exceptions%');

$container
->register(ScheduledTaskSubscriber::class)
->addArgument(new Reference('scheduled_task.repository'))
->addArgument('%frosh_sentry.report_scheduled_tasks%')
->addTag('kernel.event_subscriber');

$container->addCompilerPass(new CompilerPass\ExceptionConfigCompilerPass());
}

public function getContainerExtension(): ExtensionInterface
{
return new FroshSentryExtension();
}
}
150 changes: 150 additions & 0 deletions src/Subscriber/ScheduledTaskSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

declare(strict_types=1);

namespace Frosh\SentryBundle\Subscriber;

use MbCore\Component\Gallery\Entity\GalleryDefinition;
use Sentry\MonitorConfig;
use Sentry\MonitorScheduleUnit;
use Shopware\Core\Framework\Api\Context\SystemSource;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\ChangeSet;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\ChangeSetAware;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\ScheduledTaskCollection;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\ScheduledTaskDefinition;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\ScheduledTaskEntity;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Sentry\CheckInStatus;
use Sentry\MonitorSchedule;
use function Sentry\captureCheckIn;

class ScheduledTaskSubscriber implements EventSubscriberInterface
{
private ?ScheduledTaskCollection $scheduledTaskCollection = null;

/**
* @param EntityRepository<ScheduledTaskCollection> $scheduledTaskRepository
*/
public function __construct(
private readonly EntityRepository $scheduledTaskRepository,
private readonly bool $reportScheduledTasks
)
{
}

public static function getSubscribedEvents(): array
{
return [
PreWriteValidationEvent::class => 'triggerChangeSet',
'scheduled_task.written' => 'onScheduledTaskWritten',
];
}

public function triggerChangeSet(PreWriteValidationEvent $event): void
{
if (!$this->reportScheduledTasks) {
return;
}

foreach ($event->getCommands() as $command) {
if (!$command instanceof ChangeSetAware) {
continue;
}

if ($command->getEntityName() !== ScheduledTaskDefinition::ENTITY_NAME) {
continue;
}

$command->requestChangeSet();
}
}

public function onScheduledTaskWritten(EntityWrittenEvent $event): void
{
if (!$this->reportScheduledTasks) {
return;
}

$this->fetchScheduledTaskCollection();

foreach ($event->getWriteResults() as $writeResult) {
$scheduledTaskId = $writeResult->getPrimaryKey();
if (!\is_string($scheduledTaskId)) {
continue;
}

$scheduledTask = $this->scheduledTaskCollection?->get($scheduledTaskId);
if (!$scheduledTask instanceof ScheduledTaskEntity) {
continue;
}

$changeSet = $writeResult->getChangeSet();
if (!$changeSet instanceof ChangeSet) {
continue;
}

$checkInStatus = match ($changeSet->getAfter('status')) {
ScheduledTaskDefinition::STATUS_RUNNING => CheckInStatus::inProgress(),
ScheduledTaskDefinition::STATUS_SCHEDULED => CheckInStatus::ok(),
ScheduledTaskDefinition::STATUS_FAILED => CheckInStatus::error(),
default => null
};

if ($checkInStatus !== null) {
$this->captureCheckIn($scheduledTask, $checkInStatus);
}
}
}

private function captureCheckIn(ScheduledTaskEntity $scheduledTask, CheckInStatus $status): void
{
if($status === CheckInStatus::inProgress()) {
$scheduledTask->removeExtension('sentryCheckInId');
$checkInId = $this->getCheckInId($scheduledTask);
$scheduledTask->addArrayExtension('sentryCheckInId', [$checkInId]);
} else {
$checkInId = $this->getCheckInId($scheduledTask);
captureCheckIn(slug: $scheduledTask->getName(), status: $status, checkInId: $checkInId);
}
}

private function getCheckInId(ScheduledTaskEntity $scheduledTask): ?string
{
$extension = $scheduledTask->getExtension('sentryCheckInId');
if($extension instanceof ArrayStruct) {
return \is_string($extension->get(0)) ? $extension->get(0) : null;
}

return captureCheckIn(
slug: $scheduledTask->getName(),
status: CheckInStatus::inProgress(),
monitorConfig: $this->monitorConfig($scheduledTask)
);
}

private function monitorConfig(ScheduledTaskEntity $scheduledTask): MonitorConfig
{
$interval = max(1, (int) ($scheduledTask->getRunInterval() / 60));
$monitorSchedule = MonitorSchedule::interval($interval, MonitorScheduleUnit::minute());

return new MonitorConfig($monitorSchedule);
}

private function fetchScheduledTaskCollection(): void
{
if ($this->scheduledTaskCollection instanceof ScheduledTaskCollection) {
return;
}

$context = new Context(new SystemSource());
$criteria = new Criteria();

$this->scheduledTaskCollection = $this->scheduledTaskRepository->search($criteria, $context)->getEntities();
}
}

0 comments on commit a03d361

Please sign in to comment.