burneh
Infrastructure

When it breaks at 3am, you can find out why.

Most observability is three dashboards nobody opens and logs you grep in a panic. We wire in traces, metrics, and logs that share one trace id, so a slow request isn't a hunch: you open the trace, see the exact span that took 800ms, and read the log line that explains it, in seconds instead of an outage.

Drive it: switch the request to slow or error, click the span that stands out, and read the log line that explains it, all joined by one trace id.

trace explorer · console
request
trace 3f9a2c · 1060ms · 6 spansdegraded

click a span to inspect · slowest: postgres 750ms

postgres · INSERT orders
750ms
attributes
db.systempostgres
db.statementINSERT INTO orders (…)
db.rows1
logs · same trace id
warnslow query 750ms · budget 200ms
infoplanner: seq scan on orders(created_at)

the span, its attributes, and its logs, joined by one trace id

Three signals, one trace id.

What you just explored is the end of this. Each service is instrumented once, a collector gathers the spans, metrics, and logs off the hot path, and everything is stamped with the same trace id, so the three signals stay joined instead of scattered across three tools that don't talk.

gatewayorders-apipaymentscollectorOTel · off-pathtraceswheremetricshow muchlogswhatcorrelateby trace idquery + searchall threealerton the SLO0102030405
01
Collect

One agent per service ships spans, metrics, and logs to a collector, off the request path.

02
Three signals

Traces for where, metrics for how much, logs for what, each stored for what it's good at.

03
Correlate

Every signal carries the trace id, so a spike, its trace, and its logs all line up.

04
Query

Search across all three in one place, so debugging is a query, not a grep across boxes.

05
Alert

Page on the user-facing SLO, with the offending trace already attached to the alert.

Instrument once, correlate everywhere.

The reason the trace, the attributes, and the logs lined up in the console is this wrapper. It opens a span, so child calls nest under it automatically, and the logger reads the current trace id, so every line is already tagged. No manual plumbing per call, and nothing to forget.

instrument.ts
1// instrument.ts — one wrapper makes a request a trace
2import { trace, log } from "@/telemetry";
3
4app.post("/checkout", trace("checkout", async (span, req) => {
5 span.set("order.id", req.body.orderId); // searchable attribute
6
7 log.info("charging card", { amount }); // log carries the trace id
8 const res = await payments.charge(amount); // a child span, automatically
9 span.set("http.status", res.status);
10
11 return res; // duration recorded on exit
12}));
three signals, each for its job
Traces, the where

The path a request took and the one span that hurt. You start here, at the symptom.

Metrics, the how much

Rates, errors, and latency, cheap to keep at scale, so trends and alerts don't cost a fortune.

Logs, the what

The line of detail, structured and tied to its trace, so it's the answer, not a haystack.

One trace id is the join key. It's what turns three separate tools into one answer.

Signals you can act on, not just admire.

A wall of graphs isn't observability. Being able to answer a question you didn't pre-plan a dashboard for is. These are the properties that make it that.

One id across everything

Trace, metric, and log share a trace id, so you jump from a latency spike to the exact request and its logs.

Cardinality under control

High-cardinality data goes to traces and logs, not metric labels, so dashboards stay fast and cheap to keep.

Sampled, but keep the bad ones

Tail sampling keeps every error and slow trace and drops the boring ones, so you see what matters without the bill.

Alerts on symptoms

Page on the latency and error rate the user feels, not CPU, with the trace linked so on-call starts at the cause.

Retention that fits the signal

Long cheap metrics, shorter rich traces and logs, so you keep the trend and the detail where each is worth it.

Vendor-neutral by default

OpenTelemetry instrumentation, so your data isn't hostage to one backend you can't afford to leave.

A dashboard tells you something is wrong. The point is answering why without a redeploy.

Anyone can bolt on a metrics dashboard and feel covered, until the incident that isn't on any of the graphs. Then it's SSH in, add a log line, redeploy, and hope it happens again. Real observability is being able to ask a new question of data you already have: which requests were slow, what did that one do, why did it fail. We instrument for that, because the goal isn't more charts, it's a shorter path from alarm to answer.

Observability that shortens the path from alarm to answer.

Traces, metrics, and logs joined by one trace id, tail-sampled to keep the bad ones, alerting on the SLO with the trace attached, so on-call starts at the cause.