fusonic / sentry-cron
Automatic Sentry Cron check-ins with Symfony Scheduler
Requires
- php: >=8.2
- dragonmantank/cron-expression: ^3.4
- sentry/sentry: ^4.14
- symfony/event-dispatcher: ^6.4 || ^7.3 || ^8.0
- symfony/messenger: ^6.4 || ^7.3 || ^8.0
- symfony/scheduler: ^6.4 || ^7.3 || ^8.0
- symfony/string: ^6.4 || ^7.3 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.91
- infection/infection: ^0.31
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.4
- rector/rector: ^2.2
- tomasvotruba/type-coverage: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
About
Automatically register scheduled events from Symfony Scheduler in Sentry Cron. Only cron expressions are supported.
Install
Use composer to install the library from packagist.
composer require fusonic/sentry-cron
Configuration
Fusonic\SentryCron\SentrySchedulerEventSubscriber: arguments: $enabled: true
If you use async events, also register the messenger subscriber (see that section for why it's needed in addition to the one above):
Fusonic\SentryCron\SentryAsyncCheckInMessengerSubscriber: arguments: $enabled: true
Usage
Any regular event that is triggered with a cron expression can be used.
Event Configuration
By default, the Sentry defaults are used for monitor configurations. Per event, you can configure an attribute to use your own configuration:
use Fusonic\SentryCron\SentryMonitorConfig; #[SentryMonitorConfig(checkinMargin: 30, maxRuntime: 30, failureIssueThreshold: 5, recoveryThreshold: 5)] class SomeEvent { // ... }
Async Events
If you have an unpredictable longer-running scheduled task that batches its work and
re-dispatches itself to continue, implement AsyncCheckInScheduleEventInterface.
The scheduled event:
use Fusonic\SentryCron\SentryMonitorConfig; use Fusonic\SentryCron\AsyncCheckInScheduleEventInterface; use \Fusonic\SentryCron\AsyncCheckInScheduleEventTrait; class SomeEvent implements AsyncCheckInScheduleEventInterface { use AsyncCheckInScheduleEventTrait; // ... }
The handler, threading the check-in ID onto each follow-up batch and marking the last one:
class SomeEventHandler { private const BATCH_SIZE = 100; public function __invoke(SomeEvent $event): void { $offset = 0; // e.g.: some slow database processing $entitiesToProcess = // ... $nextEvent = (new SomeEvent(offset: $offset + self::BATCH_SIZE))->setCheckInId($event->getCheckInId()); if (count($entitiesToProcess) === 0) { $nextEvent->markAsLast(); } $this->eventBus->dispatch($nextEvent); } }
Why this needs a second subscriber. Symfony Scheduler's own
PreRunEvent/PostRunEvent/FailureEvent only fire for the message Scheduler itself
dispatched — once your handler re-dispatches a follow-up message onto a Messenger transport,
Scheduler never sees it again. SentrySchedulerEventSubscriber therefore only starts the
check-in for async events (on PreRunEvent); completing or failing it is handled entirely by
SentryAsyncCheckInMessengerSubscriber, which listens to Messenger's own
WorkerMessageHandledEvent/WorkerMessageFailedEvent instead — these fire for every batch,
on every transport hop, and are retry-aware (a failure Messenger will retry is not reported as
an error).
Those Worker events are only dispatched for messages actually consumed by a real (queued)
transport. If an async event is ever routed to sync://, or to a bus with no matching
transport at all, it's handled in-process without ever going through a Worker, so neither
subscriber can complete or fail its check-in — it will hang at in_progress until Sentry's
maxRuntime times it out and flags it as missed. Make sure every message implementing
AsyncCheckInScheduleEventInterface is routed to a transport with a real consumer.