Magento 2 Log Tracing Module

Magento log tracing: stamp every log line with a per-request trace ID. One grep finds the full request. Echoes X-Request-Id on responses. Monolog 2 and 3.

The interleaved log problem

Under load, every Magento request writes to the same log files — and the lines interleave. A dozen requests are in flight at once, each appending to var/log/system.log, and the line you care about — the exception that fires for one customer, on one product, once an hour — is buried between log entries from unrelated requests that happened to land in the same millisecond.

The fix is older than Magento: stamp every log line produced during a request with the same unique ID, then filter on it. One grep reconstructs the full request out of a busy log file, regardless of concurrency. This is log correlation, and this module wires it cleanly into Magento’s Monolog stack without touching core files.

Impact Callout

One grep finds every log line for a specific request — across system.log, debug.log, exception.log, and third-party module logs — regardless of how many other requests ran concurrently. The same ID appears in the web-server access log, on the X-Request-Id response header, and in downstream service logs for outbound cURL calls.


What it does

Five small pieces, wired by a single di.xml:

PieceFileJob
HolderService/TraceId.phpResolve the ID once per process; reuse an inbound ID or mint one
ProcessorLogger/TraceIdProcessor.phpStamp extra.trace_id on every Monolog record
HandlersLogger/Handler/Traced*.phpPrint the ID as a leading column in system.log / debug.log
PluginPlugin/ResponseHeaderPlugin.phpEcho the ID back as the X-Request-Id response header
PluginPlugin/CurlForwardPlugin.phpForward the ID (+ inbound trace context) on outbound cURL calls

Once installed, every log line carries the ID:

[2026-06-06 09:14:22] [9f2c1ad4e0b74f3a8c5d6e7f] main.INFO: Saved order 100000042
[2026-06-06 09:14:22] [9f2c1ad4e0b74f3a8c5d6e7f] main.ERROR: Payment gateway timeout

Find everything for one request:

grep -rh '9f2c1ad4e0b74f3a8c5d6e7f' var/log/

Joining a trace from upstream services

If a calling service already participates in distributed tracing, the module adopts its trace ID instead of minting a new one — so Magento’s logs carry the same ID your tracing backend shows, and line up automatically. Recognised carriers, highest priority first:

HeaderStandard / sourceWhat’s used
traceparentW3C Trace Context / OpenTelemetry / modern Dynatracethe 32-hex trace-id field (not the per-hop span-id)
X-B3-TraceId / b3B3 — Zipkin, Istio/Envoytrace-id
SAP-PASSPORTSAP end-to-end traceembedded GUID (best-effort)
X-Correlation-IDSAP BTP / CPI / CAP, generalfull value
X-DynatraceDynatrace request tagfull value
X-Request-Idgeneric correlation conventionfull value
X-Amzn-Trace-IdAWS ALB / X-Raythe Root= field
X-Cloud-Trace-ContextGoogle Cloud LBtrace-id before /
REQUEST_ID / UNIQUE_IDNginx / Apache per-nodefull value

When no upstream ID is present, the module mints one (16 random bytes, 32 hex chars — the same shape as Nginx’s $request_id).


Closing the loop at the edge

Let the web server mint the ID so the same value appears in its access log and every Magento log line.

Nginx ($request_id built-in since 1.11.0):

fastcgi_param REQUEST_ID $request_id;

log_format traced '$remote_addr - $request_time "$request" $status rid=$request_id';
access_log /var/log/nginx/access.log traced;

Apache (mod_unique_id + mod_headers):

RequestHeader setifempty X-Request-Id "%{UNIQUE_ID}e"
LogFormat "%h %l %u %t \"%r\" %>s %b rid=%{X-Request-Id}i" traced
CustomLog ${APACHE_LOG_DIR}/access.log traced

Either way, a 502 in the web-server log and the PHP fatal that caused it now share one ID.


Carrying the trace to downstream calls

Automatic for Magento’s cURL client. Every request through Magento\Framework\HTTP\Client\Curl (get() / post()) is stamped with X-Request-Id, plus any inbound traceparent, SAP-PASSPORT, X-Dynatrace, X-Correlation-ID, or B3 context forwarded verbatim — so downstream services stay on the same trace and SAP/Dynatrace/OTel continuation keeps working.

Manual for other HTTP clients (Guzzle, Laminas, etc.):

$this->http->request('POST', $endpoint, [
    'headers' => $this->traceId->propagationHeaders(),
    'json'    => $payload,
]);

CTA Bar

View article: Magento 2 Request Tracing — One ID, Every Log Line — All Modules


Who For

  • Developers debugging production incidents who need to isolate which log lines belong to the failing request among hundreds of concurrent ones.
  • Operations and support teams investigating intermittent errors, payment timeouts, or slow checkouts by customer — one ID lets you pull the exact request from a busy log.
  • Architects integrating Magento with an SAP, Dynatrace, AWS, or OpenTelemetry estate who want Magento’s logs to join the existing distributed trace without standing up an OTel collector.
  • Platform teams running multi-node Magento clusters who ship logs to OpenSearch/Loki and need a reliable join key across nodes.

Who Skip

  • Stores with very low concurrency (single-tenant dev boxes, small staging environments) where log lines are already easy to read sequentially — the module adds no harm but provides little value.
  • Teams already running an APM (New Relic, Datadog, Dynatrace) with full span-level tracing — the correlation ID answers "which request?" while a span tree answers "why was it slow?"; if you have the latter, the former is covered. The module still adds value for non-APM log files (CLI, cron, consumers), but it’s optional overhead for the web tier.

Installation

composer require brocode/module-log-tracing
bin/magento module:enable BroCode_LogTracing
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Or drop the source directly into app/code/Brocode/LogTracing and run the same commands without composer require.

Verify

Every log line should carry a leading trace ID column:

tail var/log/system.log
# [2026-06-06 09:14:22] [9f2c1ad4e0b74f3a8c5d6e7f] main.INFO: ...

The same ID comes back on the response header:

curl -sI https://your-store.test/ | grep -i x-request-id
# x-request-id: 9f2c1ad4e0b74f3a8c5d6e7f

Pass your own ID in and watch it flow end to end:

curl -sI -H 'X-Request-Id: my-debug-123' https://your-store.test/ | grep -i x-request-id
grep -rh 'my-debug-123' var/log/

Options

Trace ID without changing log format. Remove the handlers argument from etc/di.xml. The processor alone stamps the ID in the default %extra% JSON blob — enough for grep and structured-log ingestion.

Per-message IDs in a queue consumer. Call TraceId::reset() at the top of each message for a fresh ID, or TraceId::set($idFromMessage) to continue the originating request’s trace across nodes.


Compatibility

Magento versionMonologPHPStatus
2.4.83.x8.3 / 8.4Stable
2.4.72.x8.2 / 8.3Stable
2.4.62.x8.1 / 8.2Stable
2.4.4 / 2.4.52.x8.1Stable

No external dependencies beyond magento/framework and monolog/monolog.


FAQ

Does the module work with CLI commands and cron jobs?

Yes. Each PHP process — web request, bin/magento command, cron run, queue consumer — mints a fresh ID at start. Every log line that process writes shares that ID, so two overlapping cron runs no longer interleave indistinguishably.

Does it work across a multi-node cluster?

Yes, if the load balancer or Varnish mints the ID and forwards it as X-Request-Id. Every Magento node honours the inbound header (it’s first in the source list), so all nodes log under the same ID for a single request. Grepping individual nodes’ var/log only shows that node’s lines — for cross-node queries, ship logs to a central store (OpenSearch, Loki) and filter on extra.trace_id.

<type name="Vendor\Module\Logger\FooBarLogger">
    <arguments>
        <argument name="processors" xsi:type="array">
            <item name="trace_id" xsi:type="object">BroCode\LogTracing\Logger\TraceIdProcessor</item>
        </argument>
    </arguments>
</type>
Will it affect third-party module logs?

The processor is attached to Magento\Framework\Logger\Monolog by type name. Virtual loggers whose DI parent type is directly Magento\Framework\Logger\Monolog inherit it automatically — this includes the Adobe-recommended custom logger pattern, which uses virtual types of Monolog directly. Two cases are not covered automatically. First, a module that constructs its own Monolog instance from scratch (not via DI). Second — and less obvious — a module that ships its own PHP subclass of Monolog, for example class FooBarLogger extends Monolog, and creates virtual types of that subclass. Those virtual types resolve against <type name="FooBarLogger"> in DI, not against <type name="Monolog">, so the processor is absent. Fix: add one type entry in your project’s di.xml: This covers every virtual type built on FooBarLogger in one shot. Verify by checking var/log/<thatmodule>.log for the trace ID after a request.

Does it conflict with OpenTelemetry?

No. This module does log correlation (stamping a flat ID on log lines); OTel does distributed tracing (timed spans). They complement each other: the module injects trace IDs from an inbound traceparent header so Magento’s log lines carry the same ID as OTel spans — all without running an OTel collector. If you later add opentelemetry-php/contrib-auto-psr3, it fills extra the same way this processor does and can replace it.

What happens to Varnish/full-page-cached responses?

Fully cached responses bypass PHP entirely, so they won’t carry the X-Request-Id response header. Dynamic and uncached responses will.

Is the ID safe to include in a response header?

Yes. The ID is generated by bin2hex(random_bytes(16)) or taken from an inbound header, then sanitised to [A-Za-z0-9.\-] and capped at 64 characters before use. No application data leaks through it.

Related Topics

Ask a module question or suggest improvements

Tell us your use case and the module context. Spam protection is handled by ALTCHA.




More Projects