halloverden/symfony-messenger-queue-events-bundle
Symfony bundle that adds queue lifecycle events to Messenger. Hook into message dispatching, processing, success and failure to add logging, metrics or monitoring without touching your handlers. Works with any transport via the existing event dispatcher.
Reality check before you install. This is a Symfony bundle, not a Laravel package — it extends symfony/messenger, which Laravel does not use for its own queue system. It's only relevant if you're running Symfony Messenger inside a Laravel app (rare, usually via a bridge or a shared codebase), or if you're maintaining both a Laravel and a Symfony service. If you're on Laravel's native queues, stop here and use JobProcessing / JobProcessed / JobFailed instead.
Minimal steps (Symfony side):
composer require halloverden/symfony-messenger-queue-events-bundleconfig/bundles.php:
HalloVerden\MessengerQueueEventsBundle\MessengerQueueEventsBundle::class => ['all' => true],
symfony/messenger is configured with at least one transport — the bundle is useless without one.Where to look first. Open src/ and find the event classes (typically under Event/ or Events/) and the subscriber/listener that dispatches them. That subscriber is the contract: it tells you exactly which lifecycle points are covered, what payload each event carries, and whether dispatch is wrapped in a try/catch. Read DependencyInjection/ for any config tree — many small bundles have a single toggle for enabling/disabling dispatch.
First use case. Structured audit logging: listen to the "message queued/sent" event, log message class + transport name + message id as JSON, and ship it to whatever log aggregator you use. It's low-risk, proves the wiring works, and immediately gives you visibility you didn't have.
Listener with the attribute, not YAML. Assuming Symfony 5.3+/6.x:
#[AsEventListener(event: MessageQueuedEvent::class)]
final class LogQueuedMessage
{
public function __construct(private LoggerInterface $logger) {}
public function __invoke(MessageQueuedEvent $event): void
{
$this->logger->info('messenger.queued', [
'message' => $event->getEnvelope()->getMessage()::class,
'transport' => $event->getEnvelope()->getReceivedFrom()?->__toString(),
]);
}
}
Adjust the accessors to whatever the bundle actually exposes — this is the main thing to verify against the source. Ensure your services.yaml has _defaults: autoconfigure: true so #[AsEventListener] is picked up.
The four patterns that pay for themselves:
| Pattern | Trigger | Why |
|---|---|---|
| Metrics counters | queued / processed / failed | Feed Prometheus/StatsD; alert on failure rate per transport |
| Correlation IDs | queued | Stamp a UUID into a header/stamp, propagate into logs downstream |
| Dead-letter alerting | failed/retried | Catch poison messages before they silently vanish |
| Integration tests | any | Assert an event fired with a TraceableEventDispatcher |
Keep listeners dumb and fast. These fire inside the consumer loop. A listener that does a synchronous HTTP call or a heavy DB write will directly throttle your throughput. Buffer into an in-memory array and flush every N messages, or republish onto a dedicated (low-priority) transport.
Wrap it in your own adapter. Create a small MessengerAudit service that takes the bundle's events and exposes your domain API (onQueued, onFailed). When the bundle inevitably drifts or you swap it for hand-rolled middleware, only the adapter changes.
Combine with core Messenger events. The bundle earns its keep by covering gaps in Messenger's built-in WorkerMessageFailedEvent, SendMessageToTransportEvent, etc. Register both, but de-duplicate in your metrics layer — otherwise a failed message with three retries counts as four failures.
Testing. Use messenger:consume --limit=1 in a functional test, and assert against a spy listener. For unit tests, instantiate the event directly rather than booting a kernel.
composer.json, read every line of src/ before trusting it in production, and keep a fork path open. Check the composer.json constraints against your PHP and Symfony versions first — this is the #1 install failure.Illuminate\Queue\Events\*, Queue::before, Queue::failing) are richer for the Laravel queue driver; mixing the two means two event systems and no shared context.Auth::user(), tenant resolvers, and request-scoped bindings are all gone. Anything you need must be serialized into the message (headers/stamps) at dispatch time.dispatch() (your exception can kill the worker) or wraps it. If it's the former, wrap every listener body in try/catch and log the failure — never let an audit listener take down message consumption.src/EventSubscriber (or wherever listeners live) is in a resource-loaded path in services.yaml; if not, tag explicitly with kernel.event_listener.dev and test and only enable it in prod — the event volume on a busy queue is genuinely noisy.How can I help you explore Laravel packages today?