zenstruck / messenger-monitor-bundle
Batteries included UI to monitor your Messenger workers, transports, schedules, and messages.
Package info
github.com/zenstruck/messenger-monitor-bundle
Type:symfony-bundle
pkg:composer/zenstruck/messenger-monitor-bundle
Requires
- php: >=8.1
- symfony/framework-bundle: ^6.4.1|^7.0|^8.0
- symfony/messenger: ^6.4|^7.0|^8.0
- zenstruck/bytes: ^1.0
- zenstruck/collection: ^0.9
Requires (Dev)
- doctrine/doctrine-bundle: ^2.10|^3.0
- doctrine/orm: ^2.15|^3.1
- knplabs/knp-time-bundle: ^1.20|^2.0
- lorisleiva/cron-translator: ^0.4.3
- matthiasnoback/symfony-dependency-injection-test: ^4.3|^5.0|^6.0
- phpstan/phpstan: ^1.4
- phpunit/phpunit: ^9.6.0|^10.0
- symfony/config: ^6.4|^7.0|^8.0
- symfony/console: ^6.4|^7.0|^8.0
- symfony/http-client-contracts: ^3.3
- symfony/mailer: ^6.4|^7.0|^8.0
- symfony/phpunit-bridge: ^6.1|^7.0|^8.0
- symfony/process: ^6.4|^7.0|^8.0
- symfony/scheduler: ^6.4|^7.0|^8.0
- symfony/security-bundle: ^6.4|^7.0|^8.0
- symfony/serializer: ^6.4|^7.0|^8.0
- symfony/var-dumper: ^6.4|^7.0|^8.0
- zenstruck/console-test: ^1.5
- zenstruck/foundry: ^2.2
- zenstruck/messenger-test: ^1.11
Suggests
- knplabs/knp-time-bundle: For human readable timestamps and durations on your dashboard.
- lorisleiva/cron-translator: For human readable cron expressions on your dashboard.
Provides
None
Conflicts
None
Replaces
None
README
Batteries included UI to monitor your Messenger workers, transports, schedules, and messages.
| Dashboard | Message details |
|---|---|
![]() |
![]() |
| Failed transport | Schedules |
![]() |
![]() |
If the packaged UI is not to your liking, you can easily build your own with the provided tools.
Installation
composer require zenstruck/messenger-monitor-bundle
messenger:monitor Command
With zero configuration, you can run the messenger:monitor command to see information
about your running workers and transports. If storage is configured, it displays a
historical snapshot whose period can be customized with the --period option.
Storage
Note
The User Interface and History require storage. Doctrine ORM is the supported storage engine.
Configuration
-
Create a
ProcessedMessageentity that extendsZenstruck\Messenger\Monitor\History\Model\ProcessedMessagein your app:// src/Entity/ProcessedMessage.php namespace App\Entity; use Zenstruck\Messenger\Monitor\History\Model\ProcessedMessage as BaseProcessedMessage; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(readOnly: true)] #[ORM\Table('processed_messages')] class ProcessedMessage extends BaseProcessedMessage { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; public function id(): ?int { return $this->id; } }
-
Add the entity class to the bundle config:
# config/packages/zenstruck_messenger_monitor.yaml zenstruck_messenger_monitor: storage: orm: entity_class: App\Entity\ProcessedMessage # entity_manager: custom
If you use a different entity manager, set
entity_managernext toentity_class. If you already set thezenstruck_messenger_monitor.history.orm_managercontainer parameter, it takes precedence over the bundle option. -
Clear Cache:
bin/console cache:clear
-
Create and execute the migration:
bin/console doctrine:migrations:diff bin/console doctrine:migrations:migrate
Tip
For large message histories, index finished_at for pagination and time-based snapshots.
Index run_id to speed up retry lookups on the message details page. Add the relevant
attributes to your ProcessedMessage entity, then generate and run a migration.
#[ORM\Index(name: 'idx_processed_messages_finished_at', fields: ['finishedAt'])] #[ORM\Index(name: 'idx_processed_messages_run_id', fields: ['runId'])]
Usage
Once configured, consumed messages are tracked and saved. These processed messages contain a lot of useful information and can be viewed in the user interface or the provided tools.
If result normalization or history storage fails, the history entry is skipped and an error is logged when a logger is available. Messenger continues acknowledging or retrying the message according to its handler outcome.
Disable Monitoring
You may want to disable monitoring for certain messages. There are several ways to do this:
- When dispatching the message, add the
DisableMonitoringStamp:use Zenstruck\Messenger\Monitor\Stamp\DisableMonitoringStamp; /** @var \Symfony\Component\Messenger\MessageBusInterface $bus */ $bus->dispatch(new MyMessage(), [new DisableMonitoringStamp()])
- Add the
DisableMonitoringStampas a class attribute to your message (or parent class/interface):use Zenstruck\Messenger\Monitor\Stamp\DisableMonitoringStamp; #[DisableMonitoringStamp] class MyMessage { }
You may want to disable monitoring for messages that are dispatched without any handler. You can do this by using theDisableMonitoringStampwith optional constructor argumenttrue:use Zenstruck\Messenger\Monitor\Stamp\DisableMonitoringStamp; #[DisableMonitoringStamp(onlyWhenNoHandler: true)] class MyMessage { }
- Add the message class to the
excludeconfig option (can be abstract/interface):# config/packages/zenstruck_messenger_monitor.yaml zenstruck_messenger_monitor: storage: exclude: - App\Message\MyMessage
- Set
auto_stamp: falseto stop addingMonitorStampwhen dispatching messages. This lets applications without the bundle consume messages that would otherwise carry this stamp.# config/packages/zenstruck_messenger_monitor.yaml zenstruck_messenger_monitor: storage: auto_stamp: false
AddMonitorStampwhen dispatching a message you want to monitor:use Zenstruck\Messenger\Monitor\Stamp\MonitorStamp; /** @var \Symfony\Component\Messenger\MessageBusInterface $bus */ $bus->dispatch(new MyMessage(), [new MonitorStamp()]);
Symfony Scheduler messages are still stamped when the worker receives them.
Store Message Input
Input capture is disabled by default. To store the input of a monitored message, dispatch it with
EnableInputStoreStamp:
use Zenstruck\Messenger\Monitor\Stamp\EnableInputStoreStamp; $bus->dispatch(new MyMessage(), [new EnableInputStoreStamp()]);
You can also add #[EnableInputStoreStamp] to the message class, or set storage.store_input: true
to capture input for all monitored messages.
Input may include sensitive fields or email content. Add DisableInputStoreStamp as a dispatch stamp
or class attribute to exclude a message. It takes precedence over the configuration and enable stamp.
The message details show input normalized by the app's Symfony Serializer. If you disabled
framework.serializer or framework.property_access, enable them to capture ordinary message objects.
Description
The stored ProcessedMessage has a description property. This is helpful to differentiate between
messages in the user interface. By default, this is the stringified version of the message object
(if it implements \Stringable). You can add the DescriptionStamp to customize:
use Zenstruck\Messenger\Monitor\Stamp\DescriptionStamp; /** @var \Symfony\Component\Messenger\MessageBusInterface $bus */ $bus->dispatch(new MyMessage(), [new DescriptionStamp('some custom description')])
Tags
To help with filtering processed messages, they can have one or more tags. Some tags
are added automatically (like schedule if it's a scheduled message) but you can also
add your own in one of two ways:
-
When dispatching the message, add one or more
TagStamp's:use Zenstruck\Messenger\Monitor\Stamp\TagStamp; /** @var \Symfony\Component\Messenger\MessageBusInterface $bus */ $bus->dispatch(new MyMessage(), [new TagStamp('tag-1'), new TagStamp('tag-2')])
-
Add the
TagStampas a class attribute to your message (and parent class/interface):use Zenstruck\Messenger\Monitor\Stamp\TagStamp; #[TagStamp('tag-1')] #[TagStamp('tag-2')] class MyMessage { }
You can also add the
TagStampattribute to parent classes and interfaces.
Tag filters match a complete tag and its colon-separated descendants. For example, schedule
matches schedule:default:task. Characters such as % and _ are matched literally.
messenger:monitor:purge Command
If your app handles a lot of messages, the processed message database table will get very large.
The messenger:monitor:purge clears messages older than a specific date:
See Period for allowed values.
bin/console messenger:monitor:purge # by default, purges all messages older than 1 month bin/console messenger:monitor:purge --older-than all bin/console messenger:monitor:purge --older-than 1-day bin/console messenger:monitor:purge --older-than 1-week bin/console messenger:monitor:purge --status failed # purge only failed messages bin/console messenger:monitor:purge --exclude-schedules # ignore messages tagged with "schedule"
The --status option accepts success or failed. Invalid values stop the command before
any history is deleted. Omit the option to purge both statuses.
Note
Schedule this command to run daily with symfony/scheduler and
RunCommandMessage.
messenger:monitor:schedule:purge Command
If using symfony/scheduler, you might want to keep a specific # of these messages as they might run
very infrequently. When running messenger:monitor:purge, add the --exclude-schedules option to
avoid deleting schedule history. Then run messenger:monitor:schedule:purge to keep a specific number
(10 by default) of task run histories.
bin/console messenger:monitor:schedule:purge # by default, keeps 10 runs for each task bin/console messenger:monitor:schedule:purge --keep 5 # keep only 5
Use the --remove-orphans option to delete schedule task runs that are no longer associated with a schedule.
bin/console messenger:monitor:schedule:purge --remove-orphans
Note
Schedule this command to run daily with symfony/scheduler and
RunCommandMessage.
User Interface
Note
Storage must be configured for this feature.
Create a controller that extends Zenstruck\Messenger\Monitor\Controller\MessengerMonitorController
in your app:
// src/Controller/MessengerMonitorController.php namespace App\Controller; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; use Zenstruck\Messenger\Monitor\Controller\MessengerMonitorController as BaseMessengerMonitorController; #[Route('/admin/messenger')] // path prefix for the controllers #[IsGranted('ROLE_ADMIN')] // alternatively, use a firewall final class MessengerMonitorController extends BaseMessengerMonitorController { }
You can now access the dashboard at: /admin/messenger or with the route zenstruck_messenger_monitor_dashboard.
Warning
It is important that your MessengerMonitorController is only accessible by
site administrators as it contains sensitive application information. Use either the
IsGranted attribute on your controller as shown above and/or ensure the controller is
behind an access-controlled firewall
that only allows site administrators.
Install knplabs/knp-time-bundle (composer require knplabs/knp-time-bundle) to display
friendlier times and durations in the UI. Install lorisleiva/cron-translator
(composer require lorisleiva/cron-translator) to display friendlier cron expressions
for your scheduled tasks.
Custom navigation links
To add links under Application in the sidebar, override navbar_custom_menu in
templates/bundles/ZenstruckMessengerMonitorBundle/layout.html.twig.
{% extends '@!ZenstruckMessengerMonitor/layout.html.twig' %}
{% block navbar_custom_menu %}
<li class="nav-item"><a href="{{ path('app_homepage') }}" class="nav-link">Home</a></li>
{% endblock %}
Advanced Usage
Workers Service
WorkerInfo
Transports Service
TransportInfo
QueuedMessage
Schedules Service
ScheduleInfo
TaskInfo
MessageInfo
TriggerInfo
History
Note
Storage must be configured for this feature.
Storage Service
Specification
Snapshot
Others
Hide Log-Entries
While you run php bin/console messenger:consume async [-vv] you see a lot of messages like this one.
[cache] Lock acquired, now computing item "zenstruck_messenger_monitor.worker.xxx" ["key" => "zenstruck_messenger_monitor.worker.xxx"]
If you want to not display them, you can disable them with adding "!cache" to the console-channels in config/packages/monolog.yaml
when@dev: monolog: handlers: #... console: #... channels: ["!event", "!doctrine", "!console", "!cache"]
Full Default Bundle Configuration
zenstruck_messenger_monitor: storage: # Whether to add a MonitorStamp when dispatching messages to a transport. auto_stamp: true # Whether to store message input in history by default. store_input: false # Message classes to disable monitoring for (can be abstract/interface) exclude: [] orm: # Your Doctrine entity class that extends "Zenstruck\Messenger\Monitor\History\Model\ProcessedMessage" entity_class: ~ # Doctrine entity manager to use. entity_manager: default cache: pool: app.cache # If using workers in docker. You can use shared cache pool for all workers expired_worker_ttl: 3600



