Summary
TraceableMessengerTransport is a decorator over the user's real Messenger transport, but it only implements Symfony\Component\Messenger\Transport\TransportInterface. Messenger transports commonly implement several additional capability interfaces, and because those are not forwarded through the decorator, wrapping a transport with trace(...) silently removes functionality from the host application.
Reproduction
With symfony/doctrine-messenger installed:
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: 'trace(%env(MESSENGER_TRANSPORT_DSN)%)' # doctrine://default
$ php bin/console messenger:setup-transports
! [NOTE] The "async" transport does not support setup.
Without the trace(...) wrapper the same command creates the messenger_messages table as expected. The current workaround is either to set auto_setup=1 on the DSN (racy and not desirable in production) or to temporarily unwrap the decorator at deploy time.
Affected capabilities
Every Messenger capability interface that the underlying transport advertises is lost through the decorator:
| Interface |
Consequence when stripped |
SetupableTransportInterface |
messenger:setup-transports is a no-op; provisioning must rely on auto_setup=1 or be done out-of-band. |
MessageCountAwareInterface |
messenger:stats reports nothing; rate-limited worker loses backpressure info. |
ListableReceiverInterface |
messenger:failed:show / :retry / :remove do not work when the failed transport is wrapped. |
QueueReceiverInterface |
messenger:consume --queues= filter is ignored. |
KeepaliveReceiverInterface (Messenger >= 7.3) |
Long-running handlers can no longer keep the broker lease alive. |
CloseableTransportInterface |
Graceful shutdown / connection cleanup is skipped. |
Symfony\Contracts\Service\ResetInterface |
Worker per-message state reset is skipped (relevant for InMemoryTransport in tests). |
Expected behavior
A decorator should be transparent with respect to the capabilities of the object it wraps. Options:
- Single class, guarded forwarding:
TraceableMessengerTransport declares every capability interface and each method checks instanceof on the inner transport, forwarding when supported and throwing LogicException otherwise. Simple, but makes \$decorator instanceof ListableReceiverInterface structurally true even when the inner transport does not support it.
- Polymorphic decorators: the factory inspects the inner transport's implemented interfaces and returns a precisely-typed decorator so
instanceof remains truthful. More code but no capability-advertisement lie.
For control-plane methods (setup, close, reset, getMessageCount, find, all) spans are probably not desirable. keepalive is the only additional method worth instrumenting alongside the existing send / get / ack / reject spans.
Test coverage to add
- Unit: each capability method forwards to the inner transport when supported; behaves appropriately (throw or no-op, depending on the chosen design) when not.
- Functional: a
trace(doctrine://default) transport under tests/Functional/Application/ for which messenger:setup-transports creates the messenger_messages table. The existing MessengerTransportTracingTest already covers the TransportInterface surface with trace(in-memory://default) and can be extended to assert reset() forwarding.
Environment
symfony/messenger ^7.4
- bundle current
main
References
src/Instrumentation/Symfony/Messenger/TraceableMessengerTransport.php
src/Instrumentation/Symfony/Messenger/TraceableMessengerTransportFactory.php
Summary
TraceableMessengerTransportis a decorator over the user's real Messenger transport, but it only implementsSymfony\Component\Messenger\Transport\TransportInterface. Messenger transports commonly implement several additional capability interfaces, and because those are not forwarded through the decorator, wrapping a transport withtrace(...)silently removes functionality from the host application.Reproduction
With
symfony/doctrine-messengerinstalled:Without the
trace(...)wrapper the same command creates themessenger_messagestable as expected. The current workaround is either to setauto_setup=1on the DSN (racy and not desirable in production) or to temporarily unwrap the decorator at deploy time.Affected capabilities
Every Messenger capability interface that the underlying transport advertises is lost through the decorator:
SetupableTransportInterfacemessenger:setup-transportsis a no-op; provisioning must rely onauto_setup=1or be done out-of-band.MessageCountAwareInterfacemessenger:statsreports nothing; rate-limited worker loses backpressure info.ListableReceiverInterfacemessenger:failed:show/:retry/:removedo not work when the failed transport is wrapped.QueueReceiverInterfacemessenger:consume --queues=filter is ignored.KeepaliveReceiverInterface(Messenger >= 7.3)CloseableTransportInterfaceSymfony\Contracts\Service\ResetInterfaceInMemoryTransportin tests).Expected behavior
A decorator should be transparent with respect to the capabilities of the object it wraps. Options:
TraceableMessengerTransportdeclares every capability interface and each method checksinstanceofon the inner transport, forwarding when supported and throwingLogicExceptionotherwise. Simple, but makes\$decorator instanceof ListableReceiverInterfacestructurally true even when the inner transport does not support it.instanceofremains truthful. More code but no capability-advertisement lie.For control-plane methods (
setup,close,reset,getMessageCount,find,all) spans are probably not desirable.keepaliveis the only additional method worth instrumenting alongside the existingsend/get/ack/rejectspans.Test coverage to add
trace(doctrine://default)transport undertests/Functional/Application/for whichmessenger:setup-transportscreates themessenger_messagestable. The existingMessengerTransportTracingTestalready covers theTransportInterfacesurface withtrace(in-memory://default)and can be extended to assertreset()forwarding.Environment
symfony/messenger^7.4mainReferences
src/Instrumentation/Symfony/Messenger/TraceableMessengerTransport.phpsrc/Instrumentation/Symfony/Messenger/TraceableMessengerTransportFactory.php