Notes / Observability / 03 Logging Engineering / 3 Correlation Ids

3 — Cross-Signal Correlation

Metrics, logs, and traces are only more useful together than apart if something ties a specific instance of each of them back to the same event. The shared identifier that makes that jump possible — and what breaks when a hop in the call chain doesn't carry it.

Updated July 16, 2026 · §202607161816-2 ·

3 — Cross-Signal Correlation

Three dashboards open side by side — one for metrics, one for logs, one for traces — eyeballed against a shared timestamp is not observability. It’s three monitoring tools open at once. What turns them into one system is a value that shows up in all three: a trace ID.


The problem each signal alone can’t solve

SignalTells you…Doesn’t tell you…
Metricthat p99 latency spiked at 14:02which request, why
Logone service logged an error at 14:02:03what else happened across the other 11 hops
Tracethe full call tree for one specific requestwhether this request is representative

A metric is cheap because it’s pre-aggregated — and that aggregation is exactly what throws away the “which one” information (see 3 — Aggregation Composability — Why You Can't Average Percentiles for why that aggregation itself has sharp edges). A trace has the “which one” detail but only for the one request it happened to capture. Neither signal, alone, answers “the metric spiked — show me a request that caused it.” Correlation is the mechanism that answers that question.


The mechanism: a shared identifier, stamped at emission time

Request enters the system


  trace_id = 7a3f...  ◄── generated once, at the edge

        ├──► Service A span (trace_id: 7a3f...)
        │        └──► log line: {"trace_id": "7a3f...", "msg": "cache miss"}

        ├──► Service B span (trace_id: 7a3f...)
        │        └──► log line: {"trace_id": "7a3f...", "msg": "retrying upstream"}

        └──► histogram observation, tagged with
             exemplar → trace_id: 7a3f...

The W3C Trace Context standard (the traceparent header) is what makes this possible across process boundaries — every hop propagates the same trace_id (and its own span_id) to the next. This is the same propagation problem 8 — Deadline Propagation describes for deadlines: a value generated once at the edge has to survive every hop, or it’s useless past the first one.

Once every span of a request carries the same trace_id:

  • Logs emitted during that span can be structured to include the trace_id field, so a log line can be traced back to the exact request (and trace) it happened during.
  • Metrics can attach an exemplar — a sampled data point on a histogram observation that carries a trace_id — turning one bucket of an aggregate metric into a doorway straight into one concrete trace that landed in that bucket.

What this buys you

Alert fires: p99 latency > 500ms


Metric panel → click the exemplar dot on the spike


Land on one specific trace with trace_id 7a3f...


"Logs for this span" — query logs filtered to {trace_id="7a3f..."}


See the exact log line that explains the 500ms: "retrying upstream, attempt 3/3"

Three signals, three different cost/detail trade-offs, one identifier connecting them. This is the concrete mechanism behind the abstract claim in the observability-vs-monitoring debate — see 1 — What Observability Actually Means — that observability is a property of the system as instrumented, not a feature of any one tool. Without a shared correlation key stamped at emission time, no amount of tooling bolted on afterward reconstructs it; correlation has to be designed in, not queried in.


Where correlation silently breaks

The chain only holds as long as every hop propagates context. Common breakpoints:

  • Async boundaries — a request enqueues a job onto a message queue or a background worker; if the producer doesn’t inject traceparent into the message and the consumer doesn’t extract it, the trace goes cold at the queue and a brand-new trace starts on the other side.
  • Batch and cron jobs — work that isn’t triggered by a single inbound request often has no trace context to inherit at all, and needs its own deliberately-created root span.
  • Third-party or legacy services that strip unknown headers — a proxy, gateway, or not-yet-instrumented service in the middle of the call chain can drop traceparent even when every service around it propagates correctly.
  • Log lines written before the span starts — anything logged during startup, health checks, or outside request scope has no trace_id to attach, by construction, not by bug.

Each of these produces the same symptom: a trace with a gap, or a log line that can’t be pivoted to from anywhere. Diagnosing “why did correlation break here” is almost always “find the hop that didn’t propagate or didn’t attach context,” not a data-loss problem in the backend.


Where this recurs across the rest of this book

  • Pipeline — an OTel Collector processor that rewrites or drops span/log attributes can strip the very field correlation depends on; correlation has to be an explicit invariant of pipeline design, not an assumption.
  • Storage & queryDistributed Tracing Backend assembles a trace by joining spans on trace_id; Log Aggregation joins logs to traces the same way. Both depend on the identifier surviving everything upstream of them.
  • Dashboards & alerting — “jump from this alert to a representative trace” is only possible because of exemplars; see 1 — Dashboard Design.
  • FrontierAIOps / Agentic RCA automating root-cause analysis is, in effect, automating the same jump a human makes by clicking an exemplar — it depends on the same correlation identifier being present and unbroken.

Why this matters for an Observability Architect

Correlation is a design decision made at instrumentation time, and it is one of the most expensive things to retrofit — it requires every service in the request path to agree on propagation, which usually means touching every service at once rather than one team incrementally opting in. When reviewing a new service’s instrumentation, “does this propagate trace context through every outbound call, queue, and background job it makes” is a harder and more valuable question than “does it emit metrics” — a service with great metrics and broken correlation still leaves an on-call engineer manually eyeballing timestamps across three tools during an incident.

Local graph

Full graph →

Linked from 15 notes

8 — Log Aggregation

Schema-on-write vs. schema-on-read as competing bets about when to pay indexing cost, and the two different deduplication problems a log pipeline actually has to solve.

1 — What Observability Actually Means

Observability vs. monitoring, the three-pillars critique, and why observability is a property of how a system was instrumented — not a tool you bought or a dashboard you built.

2 — The Signals

Metrics, logs, traces, profiles, and events — what each is built to capture, what it costs, and which question it actually answers vs. which one people mistakenly ask it.

5 — Label & Attribute Schema Design

Cardinality budget, naming conventions, and the high-churn label traps that turn a cheap metric into a production incident — the design discipline for the labels semantic conventions don't already cover for you.

5 — Continuous Profiling

What makes always-on, sampling-based profiling cheap enough to run in production continuously, why it earns that cost mainly for hot or expensive services, and how a profile correlates back to the one trace that was running during the sample.

1 — OpenTelemetry SDKs & Semantic Conventions

OpenTelemetry is a specification and an API/SDK, not a backend — the pieces that make it up, and the semantic-convention vocabulary that lets two unrelated teams' telemetry be queried the same way.

8 — Deadline Propagation

How a client deadline must be inherited by every downstream goroutine or service call so that cancelled work stops consuming resources rather than running to completion unobserved.

9 — OTel Collector Pipeline Design

Receivers, processors, and exporters chained into a pipeline; why a platform runs more than one; and the agent/gateway topology that tail sampling specifically forces on that design.

1 — Alerting & Alert Routing

Symptom-based vs. cause-based alerting, the noise-reduction problem (dedup, grouping, correlation), and routing/escalation — the design discipline standing between a real page and a bad one.

What is Tempo

Grafana Labs' distributed tracing backend — the radical simplification vs. Jaeger's classic architecture: no dedicated index, just object storage and a trace-ID lookup, queried with TraceQL and linked from metrics via exemplars.

Chapter 1 — Observability Architecture

Metrics, logs, traces, and profiles as the four correlated signal types every observability platform is built around.

7 — Distributed Tracing Backend

How spans that arrive out of order, from different services, get assembled into one trace — and the two competing storage models (indexed search vs. object storage plus a trace-ID lookup) that trade query flexibility for cost.