Symfony
Tasks

Tasks

The bundle automatically traces Symfony Messenger messages. Each consumed message appears as a task in Traceway with its duration, status, and any child spans.

Setup

Nothing extra to configure — every consumed message is traced automatically as soon as the bundle is installed.

Symfony Messenger's default behavior is to parent the consumer span to the dispatcher's trace (so the request that dispatched the message and the worker that processed it appear as one continuous distributed trace). Traceway captures these as Tasks regardless of whether they're root spans, so the consumed message shows up in the Tasks page either way — flagged with a Non-root chip when it was triggered from another trace.

If you'd rather have each consumed message start its own independent trace, opt in:

config/packages/open_telemetry.yaml
open_telemetry:
    messenger_root_spans: true

This is mostly a presentation choice. With messenger_root_spans: true, the consumer is a root Task (no chip) and the cross-trace link to the dispatcher is broken. Without it, the consumer is a non-root Task and the dispatcher → consumer relationship is preserved in the distributed-trace view.

What Gets Captured

For each consumed message, the bundle records:

  • Span name — the message class name (e.g., EmailSendMessage process)
  • Duration — total handler execution time
  • Transport — which transport delivered the message (e.g., async)
  • Errors — exceptions are recorded with stack traces and the span is marked as failed
  • Child spans — any spans created inside the handler nest under the task span

Adding Attributes in Handlers

Since the bundle activates the task span before your handler runs, use Span::getCurrent() to add custom attributes:

use OpenTelemetry\API\Trace\Span;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
 
#[AsMessageHandler]
class EmailSendHandler
{
    public function __invoke(EmailSendMessage $message): void
    {
        $span = Span::getCurrent();
 
        $span->setAttribute('email.to', $message->to);
        $span->setAttribute('email.subject', $message->subject);
 
        $this->mailer->send($message);
    }
}

Child Spans in Handlers

For handlers with distinct sub-operations, inject TracingInterface to create child spans. They automatically nest under the task's root span:

use OpenTelemetry\API\Trace\SpanKind;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Traceway\OpenTelemetryBundle\TracingInterface;
 
#[AsMessageHandler]
class PaymentProcessorHandler
{
    public function __construct(
        private readonly TracingInterface $tracing,
    ) {}
 
    public function __invoke(PaymentProcessorMessage $message): void
    {
        $this->tracing->trace('validate.payment', function () use ($message) {
            $this->validate($message);
        }, ['payment.amount' => $message->amount, 'payment.currency' => $message->currency]);
 
        $this->tracing->trace('charge.gateway', function () use ($message) {
            $this->chargeGateway($message);
        }, kind: SpanKind::KIND_CLIENT);
    }
}

Dispatch Tracing

The bundle also traces the dispatch side — when your controller or service dispatches a message, a PRODUCER span is created. W3C trace context is injected into the message envelope so the consumer span links back to the dispatching trace (unless messenger_root_spans is true, in which case the consumer creates an independent root span). Either way, Traceway captures the consumer as a Task and surfaces the cross-trace link when one exists.