Logs & Trace Correlation
TraceCorrelationProcessor stamps the current OTel span's trace_id and span_id onto every Nexus log record's extra field. Log lines emitted from inside an actor handler carry the same trace ID as the OTel trace, enabling log-trace correlation in tools like Grafana Loki + Tempo.
How to register
Add TraceCorrelationProcessor when building your logger:
use Monadial\Nexus\Logger\Formatter\JsonFormatter;
use Monadial\Nexus\Logger\Handler\ConsoleHandler;
use Monadial\Nexus\Logger\Level;
use Monadial\Nexus\Logger\NexusLogger;
use Monadial\Nexus\Observability\Logger\TraceCorrelationProcessor;
$logger = NexusLogger::create($system, 'app')
->minLevel(Level::Info)
->processor(new TraceCorrelationProcessor($observability))
->handler(new ConsoleHandler(STDOUT, new JsonFormatter()))
->build();
The processor must be registered before any handlers so the extra fields are present when the handler formats the record.
What it adds
Each log record produced inside a traced actor handler receives three extra fields:
| Field | Type | Example | Description |
|---|---|---|---|
extra.trace_id | string (hex) | 4bf92f3577b34da6a3ce929d0e0e4736 | OTel trace ID |
extra.span_id | string (hex) | 00f067aa0ba902b7 | OTel span ID for the current actor message span |
extra.trace_flags | int | 1 | OTel trace flags (1 = sampled) |
Behaviour when observability is disabled
When withObservability() has not been called, TraceCorrelationProcessor checks whether the current span is recording. The OTel no-op span reports isRecording() === false, so the processor skips stamping and the log record is emitted without trace fields. There is no error.
Relationship to ServerSpanMiddleware
In the HTTP layer, ServerSpanMiddleware (from nexus-observability-http) extracts W3C Trace Context headers from the incoming request and starts a server span as the active context before passing control to the route handler. TraceCorrelationProcessor reads this active span when log records are created.
Result: every log line emitted anywhere in the HTTP request lifecycle — inside the route handler, inside actor handlers, inside repositories — carries the HTTP server span's trace_id. A single trace ID links all related log lines and all OTel spans.
Grafana integration
To make trace_id clickable in Grafana Loki:
- Configure your Monolog JSON formatter to include the
extramap in its output. - In Grafana, open the Loki datasource settings and add a Derived field:
- Name:
TraceID - Regex:
"trace_id":"([a-f0-9]{32})" - URL:
http://tempo:3200/trace/$${__value.raw}(adjust for your Tempo URL)
- Name:
- Grafana will render a Tempo link next to each log line that contains a
trace_id.
If you use structured JSON logging, output trace_id as a top-level field rather than inside extra to simplify the Grafana regex and make the field available for Loki label extraction.
Combining with PSR-14 log events
If your application dispatches PSR-14 events that result in log output (e.g., domain events emitted via EventDispatcherInterface), the processor still works correctly provided those events are dispatched from within an active span — that is, from inside an actor handler or from within the HTTP request lifecycle under ServerSpanMiddleware.