Connecting Logs and Traces
The idea in one sentence
Section titled “The idea in one sentence”A tracer already knows which span is active while your code handles a request, so log injection is simply the tracer handing that active trace_id (and span_id) to your logging library, so every log line written during that request carries the identifiers needed to find it again.
Without it: a log line with nowhere to go
Section titled “Without it: a log line with nowhere to go”2026-07-08T10:14:02Z ERROR Failed to charge card for order 48213This line tells you something failed — it does not tell you which downstream call was slow, which service further down the chain actually produced the error, or what else happened during the same request. Without an identifier stamped onto both the log and the trace, correlating them means guessing by timestamp and hoping nothing else touched that host in the same second.
The general mechanism: a log integration reads the active span
Section titled “The general mechanism: a log integration reads the active span”Every tracer keeps the currently active span in some form of request-scoped context — thread-local in a threaded runtime, async-local (or continuation-local) in an event-loop runtime. A log injection integration is a small hook into your logging library — a formatter, a processor, or a patched logger method — that runs on every log record and asks the tracer “what span is active right now?” If there is one, it copies that span’s trace_id and span_id onto the record before it is emitted. Enable it once, and every log statement already in the codebase benefits, without being touched individually.
One concrete example: Node.js
Section titled “One concrete example: Node.js”require('dd-trace').init({ log_injection: true});That flag is the entire integration step for a Node.js service using dd-trace. Once it is set, any log emitted through a supported logging library (pino, winston, bunyan) automatically gets the active trace context attached to its structured output — no change to any individual logger.info(...) or logger.error(...) call anywhere in the app.
The dd.* convention: identity riding along in the log line
Section titled “The dd.* convention: identity riding along in the log line”{ "level": "error", "message": "Failed to charge card for order 48213", "dd": { "trace_id": "6746734253672554013", "span_id": "3993638273767594310", "env": "production", "service": "checkout-service", "version": "2.4.1" }}Datadog tracers converge on a dd.* namespace for these injected fields. dd.trace_id and dd.span_id are the pivot keys — they name the exact trace and the exact span that was executing when this line was written. dd.env, dd.service, and dd.version are the unified service tagging triple riding along too, which means this single log line carries enough identity to also be found through the structured logging and facets built out in the Log Management module — it is not just correlatable to a trace, it is a first-class faceted log event in its own right.
The payoff: one click, not a manual search
Section titled “The payoff: one click, not a manual search”Before log injection, an error-severity log line sits by itself, with no easy way to see the request around it — what called this service, what it called next, where the time went. After log injection, because the log carries dd.trace_id, Datadog can put a “view trace” pivot directly on that log line in the Logs Explorer — one click, and you land on the exact distributed trace, with every span across every service that took part in that request, spans that never looked at the log at all.
flowchart LR A[Incoming request] --> B[Tracer starts span] B --> C["App code calls logger.error(...)"] C --> D["Log injection hook reads active span"] D --> E["Log emitted with dd.trace_id, dd.span_id, dd.env/service/version"] E --> F[Datadog Log Management] F -->|click dd.trace_id| G[APM distributed trace view]