php yaml 110 lines · 4 tabs

Propagate a Correlation ID Through Symfony Requests and Monolog Logs

Shared by codesnips Sep 2026
4 tabs
<?php

namespace App\Correlation;

final class CorrelationIdStorage
{
    private ?string $correlationId = null;

    public function get(): ?string
    {
        return $this->correlationId;
    }

    public function set(string $correlationId): void
    {
        $this->correlationId = $correlationId;
    }

    public function reset(): void
    {
        $this->correlationId = null;
    }
}
4 files · php, yaml Explain with highlit

This snippet wires a correlation id through the entire lifecycle of a Symfony request so every log line, and every downstream response, can be tied back to a single client interaction. A correlation id (sometimes called a request id or trace id) is the backbone of debuggable distributed systems: when a user reports an error, grepping logs for one opaque token surfaces every line that request produced, across every service that forwarded the header.

The design keeps the id in one place. CorrelationIdStorage is a tiny mutable holder registered as a service; both the listener and the processor depend on it rather than passing the id around explicitly. This is the idiomatic Symfony approach to sharing per-request state without leaking it into method signatures, and because Symfony's default container reuses services within a request it behaves like a scoped value.

In RequestCorrelationListener, the onKernelRequest handler runs only on the main request (checked via $event->isMainRequest()) to avoid overwriting the id during sub-requests such as ESI fragments or forwards. It reads an incoming X-Correlation-Id header if a trusted upstream already set one, otherwise it mints a fresh UUID. This accept-or-generate rule is what lets the id survive across service boundaries. The onKernelResponse handler echoes the id back on the response header so clients and gateways can capture it. Both handlers are bound with #[AsEventListener] and explicit priorities so the id is established very early and written back late.

CorrelationIdProcessor implements Monolog's ProcessorInterface; its __invoke merges the current id into extra on every LogRecord, meaning no logging call site needs to remember to include it. Because the processor pulls from the same storage the listener populated, the value is always current.

The services.yaml tab shows the wiring: the storage is a plain service, and the processor is tagged with monolog.processor so Monolog picks it up automatically. A subtle pitfall worth noting is long-running workers (Messenger, RoadRunner): the storage must be reset between messages or ids will bleed across jobs, which is why keeping it a mutable single object rather than a constructor-injected constant matters.


Related snips

Share this code

Here's the card — post it anywhere.

Propagate a Correlation ID Through Symfony Requests and Monolog Logs — share card
Link copied