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
| Signal | Tells you… | Doesn’t tell you… |
|---|---|---|
| Metric | that p99 latency spiked at 14:02 | which request, why |
| Log | one service logged an error at 14:02:03 | what else happened across the other 11 hops |
| Trace | the full call tree for one specific request | whether 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_idfield, 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
traceparentinto 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
traceparenteven 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_idto 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 & query —
Distributed 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.
- Frontier — AIOps / 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
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.
Related notes
3 — Aggregation Composability — Why You Can't Average Percentiles
Some statistics merge correctly across shards, replicas, and time windows — sum, count, max. Percentiles do not. The distinction that decides whether a fleet-wide dashboard is trustworthy or quietly wrong.
2 — Tail Latency
Why the p99/p999 request latency matters more than the average in distributed systems — a single slow dependency in a fan-out can dominate the response time even when most calls are fast.
8 — Query Sharding
Splitting a single logical query into N independently-executable sub-queries that run in parallel and merge into one result — how Grafana Mimir and Loki answer high-cardinality queries within tight SLOs without adding more data shards.
9 — Fan-Out Metrics and Trace Shape
The Prometheus metrics that instrument a fan-out pattern (width, shard latency, aggregation, partial results, cancelled workers, hedged requests), the OTel trace waterfall shape that reveals the tail shard, and when requests fan out to multiple shards vs route to a single one.