2323use Symfony \Component \Messenger \Stamp \RedeliveryStamp ;
2424use Symfony \Contracts \Service \ServiceSubscriberInterface ;
2525
26- class WorkerMessageEventSubscriber implements EventSubscriberInterface, ServiceSubscriberInterface, InstrumentationTypeInterface
26+ /**
27+ * Creates consumer-side spans for messages processed by the Symfony Messenger worker,
28+ * with support for both auto and attribute-based instrumentation modes,
29+ * trace context propagation from the producer, and retry/failure metadata.
30+ */
31+ final class WorkerMessageEventSubscriber implements EventSubscriberInterface, ServiceSubscriberInterface, InstrumentationTypeInterface
2732{
28- private ? InstrumentationTypeEnum $ instrumentationType = null ;
33+ private InstrumentationTypeEnum $ instrumentationType = InstrumentationTypeEnum::Auto ;
2934
3035 public function __construct (
3136 private readonly MultiTextMapPropagator $ propagator ,
3237 private readonly TracerInterface $ tracer ,
3338 /** @var ServiceLocator<TracerInterface> */
3439 private readonly ServiceLocator $ tracerLocator ,
35- private readonly LoggerInterface $ logger ,
40+ private readonly TraceStampPropagator $ traceStampPropagator ,
41+ private readonly ?LoggerInterface $ logger = null ,
3642 ) {
3743 }
3844
@@ -56,36 +62,43 @@ public static function getSubscribedEvents(): array
5662 ];
5763 }
5864
65+ /**
66+ * @return class-string[]
67+ */
5968 public static function getSubscribedServices (): array
6069 {
61- return [];
70+ return [TracerInterface::class ];
6271 }
6372
6473 public function startSpan (WorkerMessageReceivedEvent $ event ): void
6574 {
6675 $ message = $ event ->getEnvelope ()->getMessage ();
76+ $ traceable = $ this ->parseAttribute ($ message );
77+
78+ if (!$ this ->isAutoTraceable () && !$ this ->isAttributeTraceable ($ traceable )) {
79+ $ this ->logger ?->debug(sprintf ('Message "%s" is not traceable, skipping span creation ' , get_class ($ message )));
6780
68- if (false === $ this ->isAutoTraceable () && false === $ this ->isAttributeTraceable ($ message )) {
6981 return ;
7082 }
7183
7284 // Clean up any lingering scope from a previous message that was not
73- // properly ended (e.g. worker killed, unhandled error in another subscriber).
85+ // properly ended (e.g. an exception in another high-priority subscriber
86+ // prevented the handled/failed event from firing).
7487 $ previousScope = Context::storage ()->scope ();
7588 if (null !== $ previousScope ) {
7689 $ previousScope ->detach ();
7790 $ orphanedSpan = Span::fromContext ($ previousScope ->context ());
7891 $ orphanedSpan ->setStatus (StatusCode::STATUS_ERROR , 'Span was not properly ended ' );
7992 $ orphanedSpan ->end ();
80- $ this ->logger ->warning (sprintf ('Cleaned up orphaned span "%s" ' , $ orphanedSpan ->getContext ()->getSpanId ()));
93+ $ this ->logger ? ->warning(sprintf ('Cleaned up orphaned span "%s" ' , $ orphanedSpan ->getContext ()->getSpanId ()));
8194 }
8295
8396 // ensure propagation from incoming trace
84- $ context = $ this ->propagator ->extract ($ event ->getEnvelope (), new TraceStampPropagator ( $ this ->logger ) );
97+ $ context = $ this ->propagator ->extract ($ event ->getEnvelope (), $ this ->traceStampPropagator );
8598
8699 $ messageClass = get_class ($ message );
87100
88- $ span = $ this ->getTracer ($ message )
101+ $ span = $ this ->getTracer ($ traceable )
89102 ->spanBuilder (sprintf ('%s %s ' , $ event ->getReceiverName (), $ messageClass ))
90103 ->setParent ($ context )
91104 ->setSpanKind (SpanKind::KIND_CONSUMER )
@@ -99,7 +112,7 @@ public function startSpan(WorkerMessageReceivedEvent $event): void
99112 $ span ->setAttribute ('symfony.messenger.bus.name ' , $ busNameStamp ->getBusName ());
100113 }
101114
102- $ this ->logger ->debug (sprintf ('Starting span "%s" ' , $ span ->getContext ()->getSpanId ()));
115+ $ this ->logger ? ->debug(sprintf ('Starting span "%s" ' , $ span ->getContext ()->getSpanId ()));
103116
104117 Context::storage ()
105118 ->attach (
@@ -113,14 +126,16 @@ public function endSpanWithSuccess(WorkerMessageHandledEvent $event): void
113126 $ scope = Context::storage ()->scope ();
114127
115128 if (null === $ scope ) {
129+ $ this ->logger ?->debug('No active scope ' );
130+
116131 return ;
117132 }
118133
119134 $ scope ->detach ();
120135
121136 $ span = Span::fromContext ($ scope ->context ());
122137 $ span ->setStatus (StatusCode::STATUS_OK );
123- $ this ->logger ->debug (sprintf ('Ending span "%s" ' , $ span ->getContext ()->getSpanId ()));
138+ $ this ->logger ? ->debug(sprintf ('Ending span "%s" ' , $ span ->getContext ()->getSpanId ()));
124139 $ span ->end ();
125140 }
126141
@@ -129,6 +144,8 @@ public function endSpanOnError(WorkerMessageFailedEvent $event): void
129144 $ scope = Context::storage ()->scope ();
130145
131146 if (null === $ scope ) {
147+ $ this ->logger ?->debug('No active scope ' );
148+
132149 return ;
133150 }
134151
@@ -155,7 +172,7 @@ public function endSpanOnError(WorkerMessageFailedEvent $event): void
155172
156173 $ span ->setStatus (StatusCode::STATUS_ERROR , $ exception ->getMessage ());
157174
158- $ this ->logger ->debug (sprintf ('Ending span "%s" ' , $ span ->getContext ()->getSpanId ()));
175+ $ this ->logger ? ->debug(sprintf ('Ending span "%s" ' , $ span ->getContext ()->getSpanId ()));
159176 $ span ->end ();
160177 }
161178
@@ -167,11 +184,15 @@ private function parseAttribute(object $message): ?Traceable
167184 return $ attribute ?->newInstance();
168185 }
169186
170- private function getTracer (object $ message ): TracerInterface
187+ private function getTracer (? Traceable $ traceable ): TracerInterface
171188 {
172- $ traceable = $ this ->parseAttribute ($ message );
173-
174189 if (null !== $ traceable ?->tracer) {
190+ if (!$ this ->tracerLocator ->has ($ traceable ->tracer )) {
191+ $ this ->logger ?->warning(sprintf ('Tracer "%s" not found in service locator, using default tracer ' , $ traceable ->tracer ));
192+
193+ return $ this ->tracer ;
194+ }
195+
175196 return $ this ->tracerLocator ->get ($ traceable ->tracer );
176197 }
177198
@@ -183,9 +204,9 @@ private function isAutoTraceable(): bool
183204 return InstrumentationTypeEnum::Auto === $ this ->instrumentationType ;
184205 }
185206
186- private function isAttributeTraceable (object $ message ): bool
207+ private function isAttributeTraceable (? Traceable $ traceable ): bool
187208 {
188209 return InstrumentationTypeEnum::Attribute === $ this ->instrumentationType
189- && $ this -> parseAttribute ( $ message ) instanceof Traceable ;
210+ && null !== $ traceable ;
190211 }
191212}
0 commit comments