manomano-tech/correlation-ids-monolog
Monolog processor that injects correlation IDs into every log record, with built-in support for ManoMano's correlation ID conventions and propagation, making it easy to trace requests across services in Laravel and PHP applications.
This is a thin glue package: it contains (almost certainly) a single Monolog processor class that reads the current correlation ID from manomano-tech/correlation-ids and copies it onto every LogRecord. Treat it as ~50 lines of source you can read in a minute, not a framework.
1. Install
composer require manomano-tech/correlation-ids-monolog
This pulls in manomano-tech/correlation-ids transitively. Note the last release is from September 2018 — expect PHP 7.x / Monolog 1.x era code. Verify compatibility against your Monolog (2/3) and PHP (8.x) before adopting; if either fails, vendor a fork instead of waiting for upstream.
2. Find the processor class first
Browse vendor/manomano-tech/correlation-ids-monolog/src/. The processor is the only meaningful class. Confirm the exact FQCN and — critically — which key it writes to (extra.correlation_id, context.correlation_id, etc.). Everything downstream (formatter, index mapping, log queries) depends on that key, so get it from the source rather than guessing:
composer show manomano-tech/correlation-ids-monolog
grep -r "record\[" vendor/manomano-tech/correlation-ids-monolog/src
3. Set the correlation ID as early as possible
The parent package holds the ID; the processor just reads it. Seed it at the request boundary:
use CorrelationIds\CorrelationIds; // confirm namespace in vendor/
$correlationId = $_SERVER['HTTP_X_CORRELATION_ID']
?? CorrelationIds::generate(); // confirm generator method name
CorrelationIds::getInstance()->setCorrelationId($correlationId);
4. Register the processor with Monolog
$logger = new \Monolog\Logger('app');
$logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout'));
$logger->pushProcessor(new CorrelationIds\Monolog\Processor\CorrelationIdProcessor());
First use case worth validating: a single inbound HTTP request produces log lines across web + queue worker + downstream service, all tagged with the same ID, so you can grep one ID and see the whole trace. If you can't demonstrate that end-to-end in an afternoon, the integration isn't done.
Processor placement: global, not per-channel. In practice you want the ID on every channel. Push it on the logger factory / root logger so all children inherit it, rather than re-registering per channel.
Symfony: tag as a Monolog processor. This is the idiomatic integration and avoids manual wiring:
# config/services.yaml
services:
CorrelationIds\Monolog\Processor\CorrelationIdProcessor:
tags:
- { name: monolog.processor }
Symfony: middleware/event listener for the ID. Set the correlation ID in a kernel request listener (kernel.request, high priority) so it's populated before anything else logs. If the parent package ships an HTTP middleware, prefer it — it also handles inbound header parsing and outbound propagation for you.
Format the ID into the message line. With a line formatter:
monolog:
handlers:
main:
formatter: monolog.formatter.line
# "%datetime% %channel% %level_name% [%extra.correlation_id%] %message%\n"
For JsonFormatter, extra fields are serialized automatically — no formatter config needed, and this is the better choice if you ship logs to ELK/Loki/Datadog where you want the ID as a queryable field rather than a substring.
Propagate into async work. Correlation IDs die at the queue boundary unless you carry them. Serialize the ID into the job payload on dispatch and restore it at the top of the worker:
// dispatch
$payload['_correlation_id'] = CorrelationIds::getInstance()->getCorrelationId();
// worker bootstrap
CorrelationIds::getInstance()->setCorrelationId($payload['_correlation_id'] ?? CorrelationIds::generate());
The same applies to HTTP clients calling downstream services — the parent package likely has an outbound-header helper; if not, add the header manually in your Guzzle middleware.
CLI and cron. No inbound header exists. Generate an ID at the top of every command so long-running jobs are traceable, and log it once at startup.
If the parent package is plural ("correlation-ids"), check whether it models a chain (e.g., request id + parent id + batch id). If so, map the whole set into extra rather than flattening to one string — you'll regret losing the parent link when debugging a fan-out.
If you already run OpenTelemetry / a tracing SDK, skip this package. A three-line custom processor that reads your existing trace ID gives you the same result with one fewer dependency and no singleton.
null or an empty key. Put ID initialization as early as the framework allows.extra or context, and the exact sub-key name is the contract your log queries, index mapping, and formatter patterns depend on. Changing it later is a breaking change to dashboards.%extra.correlation_id% will render blank (not error) if the key is spelled differently — you'll silently ship unusable logs. Assert once in a test that a log line contains a non-empty ID.setUp()/tearDown() or tests will pass alone and fail in suite order.- or omit the field entirely; a blank [] in every line trains people to ignore it.X-Correlation-ID) across all services before rollout. A mismatched header between two teams is the most common reason this "doesn't work" in production.__invoke(array $record): array that returns $record with your fields added. It's the same code, without the abandoned dependency.How can I help you explore Laravel packages today?