aubes / correlation-bundle
Correlation ID propagation for Symfony: storage, generation, HTTP, and optional integrations for Monolog, Messenger, Mercure, and HTTP Client
Package info
github.com/aubes/correlation-bundle
Type:symfony-bundle
pkg:composer/aubes/correlation-bundle
Requires
- php: ^8.2
- psr/log: ^2.0 | ^3.0
- symfony/config: ^6.4 | ^7.4 | ^8.0
- symfony/console: ^6.4 | ^7.4 | ^8.0
- symfony/dependency-injection: ^6.4 | ^7.4 | ^8.0
- symfony/http-foundation: ^6.4 | ^7.4 | ^8.0
- symfony/http-kernel: ^6.4 | ^7.4 | ^8.0
- symfony/service-contracts: ^3.0
- symfony/uid: ^6.4 | ^7.4 | ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.1
- monolog/monolog: ^3.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.0 | ^12.0 | ^13.0
- symfony/http-client: ^6.4 | ^7.4 | ^8.0
- symfony/mercure-bundle: ^0.3.8 | ^0.4
- symfony/messenger: ^6.4 | ^7.4 | ^8.0
- twig/twig: ^3.0 | ^4.0
Suggests
- monolog/monolog: Required for Monolog log record injection
- symfony/http-client: Required for outgoing HTTP client header propagation
- symfony/mercure-bundle: Required for Mercure SSE event injection
- symfony/messenger: Required for Messenger message stamp propagation
- twig/twig: Required for the correlation_id() Twig function
README
Lightweight distributed tracing for Symfony. Propagates a correlation ID across your entire stack (HTTP, HTTP Client, Monolog, Messenger, Mercure, Twig) without the overhead of a full APM solution.
Worker-mode ready: the storage implements ResetInterface and is scoped per request/message. Works out of the box with FrankenPHP worker mode and Messenger workers.
Requirements
- PHP >= 8.2
- Symfony 6.4 / 7.4 / 8.x
Installation
composer require aubes/correlation-bundle
The bundle auto-registers via Symfony Flex.
How it works
The bundle captures or generates a correlation ID at the start of each HTTP request or console command, stores it in a request-scoped storage, and propagates it to all configured integrations.
Core (always active)
- Storage:
CorrelationIdStorageisfinal, self-materializes a UUID v7 via the configured generator on firstget(), and memoizes the result - Contract:
CorrelationIdProviderInterface::get()always returns a valid, non-null string - Validation:
set()validates its input (printable ASCII, 1-255 chars) and throwsInvalidCorrelationIdExceptionon invalid values. Last write wins - Reset: implements
ResetInterfacefor worker/long-running process safety - Console: correlation ID generated automatically per command, overridable via
--correlation-id
HTTP (always active)
- Captures
X-Correlation-Idfrom incoming requests (configurable header name) - Generates a new ID when the header is missing
- Echoes the ID in the response header
Optional integrations
Each integration activates automatically when its dependency is installed, and can be explicitly disabled.
| Integration | Dependency | What it does |
|---|---|---|
| HTTP Client | symfony/http-client |
Forwards the correlation ID as a header on outgoing HTTP requests. Any caller-provided correlation header is overwritten (the storage is the single source of truth) |
| Monolog | monolog/monolog |
Injects the ID into every log record's extra array |
| Messenger | symfony/messenger |
Stamps the ID on dispatch, restores it on consume |
| Mercure | symfony/mercure-bundle |
Injects the ID into JSON object payloads |
| Twig | twig/twig |
Provides a correlation_id() template function |
Configuration
# config/packages/correlation.yaml correlation: # Core generator: 'Aubes\CorrelationBundle\Generator\UuidCorrelationIdGenerator' uuid_version: 7 # 4, 6, or 7 # HTTP http: header_name: 'X-Correlation-Id' send_response_header: true # Optional integrations http_client: enabled: true header: 'X-Correlation-Id' force_header: true # false = caller-provided header takes precedence clients: ['http_client'] monolog: enabled: true field_name: 'correlation_id' channels: [] # empty = all channels handlers: [] # empty = all handlers messenger: enabled: true buses: [] # empty = all buses mercure: enabled: true field_name: 'correlation_id' hubs: ['default'] twig: enabled: true
All values shown above are defaults. Zero configuration is needed for the common case.
Interfaces
CorrelationIdProviderInterface
Read-only access. Inject this when you only need to read the current ID.
use Aubes\CorrelationBundle\Storage\CorrelationIdProviderInterface; final class MyService { public function __construct(private readonly CorrelationIdProviderInterface $provider) {} public function doSomething(): void { $correlationId = $this->provider->get(); // guaranteed non-null } }
CorrelationIdStorageInterface
Full access: read, write, and reset. Extends CorrelationIdProviderInterface and ResetInterface.
public function get(): string; public function set(string $id): void; // throws InvalidCorrelationIdException public function reset(): void;
CorrelationIdGeneratorInterface
public function generate(): string;
Extension points
Swap the generator
use Aubes\CorrelationBundle\Generator\CorrelationIdGeneratorInterface; final class MyGenerator implements CorrelationIdGeneratorInterface { public function generate(): string { return bin2hex(random_bytes(16)); } }
correlation: generator: App\MyGenerator
Note: the service referenced by
generatormust implementAubes\CorrelationBundle\Generator\CorrelationIdGeneratorInterface.
Seed the ID from a custom context
Inject CorrelationIdStorageInterface and call $storage->set($id) before the first downstream read:
use Aubes\CorrelationBundle\Exception\InvalidCorrelationIdException; try { $storage->set($idFromExternalSource); } catch (InvalidCorrelationIdException) { // untrusted source: fall back to the generator }
Debug
The correlation:debug command displays active integrations, the generator class, and the current correlation ID:
php bin/console correlation:debug
When the Web Profiler is enabled, a correlation ID panel shows the ID source (generated vs provided) and the list of active integrations.
License
MIT