Leer en español · Lire en français · Auf Deutsch lesen
Section 9.6 of 9.8 · Intermediate · 26 min read
Observability: logs, metrics and traces
Video lesson
5 min · narrated, with captionsListen only (audio)
Read the transcript
Welcome to Section nine point six. When a system misbehaves in production, you need to find out why, quickly. This lesson is about building systems that let you do that.
A car dashboard shows a few dials and warning lights: speed, fuel, engine temperature. That's monitoring. It tells you about problems someone predicted. But when the car makes a strange noise that no light explains, the dashboard can't help. A mechanic plugs in a diagnostic computer and reads detailed records of what every part was doing, to work out a fault nobody anticipated. Observability means building software so that this kind of investigation is always possible: every part keeps clear records, and one journey can be followed from part to part.
Monitoring answers questions you knew to ask in advance: is the disk ninety per cent full? Observability is a property of the system: can you explain behaviour nobody predicted? An order might be slow because a new menu feature makes one query slow, but only for restaurants with more than five hundred items. No one wrote a dashboard for that. Observability lets you slice the telemetry by any attribute until the pattern appears. So it must be designed in, through consistent instrumentation and shared identifiers. It can't be bolted on during an incident.
Metrics are numbers aggregated over time, such as requests per second. They're cheap, and ideal for dashboards and alerts, but they can't tell you about one particular request. Logs record individual events. Write them as structured fields, with agreed names, rather than free text. And never log secrets or unnecessary personal data. Traces show one request's path through many services, and where its time went. And profiles show where the code itself spends processor time or memory.
A common metrics mistake is averaging percentiles. The menu service runs on two instances. Instance A is healthy, with a ninety ninth percentile of a hundred and eighty milliseconds. Instance B has a noisy neighbour. Three thousand of its ten thousand requests are slow, and its ninety ninth percentile is one thousand and eighty milliseconds. Average the two, and the dashboard shows six hundred and thirty milliseconds. But the slowest two hundred of all twenty thousand requests are on instance B. The true figure is one thousand and sixty milliseconds. The dashboard understates the tail by four hundred and thirty milliseconds. Percentiles can't be averaged. Instead, add up histogram buckets across servers, and read the percentile from the merged result.
Two short checklists cover most of what to measure. For anything that serves requests, measure rate, errors and duration. That's the RED method, from Tom Wilkie. For every resource, such as a processor, a disk or a connection pool, measure utilisation, saturation and errors. That's the USE method, from Brendan Gregg. Google's four golden signals, latency, traffic, errors and saturation, are in effect RED plus saturation. In practice, RED describes what users experience, and USE explains why. Rising payment latency might be explained by a connection pool that's full, with forty requests waiting.
Across services there's no single stack trace, so distributed tracing rebuilds one. Every request gets a trace identifier at the edge, and every unit of work becomes a span. This trace shows one place order request, which took nine hundred and forty milliseconds. Pricing took just seventy five milliseconds. The payment service took seven hundred and sixty, and inside it, the external card provider took seven hundred and twenty. That's where the time went: not in the platform's own services. Tracing depends on every service passing the trace context on, using the standard traceparent header. One service that drops it breaks every trace that passes through. OpenTelemetry is now the vendor-neutral standard for producing traces, metrics and logs.
Telemetry can cost more than the system it observes. Every distinct combination of metric labels is a separate time series. One request duration histogram on the order service already makes a hundred and twenty six thousand series. Add a label for each of forty thousand restaurants, and that becomes about five billion. Per restaurant questions belong in traces and logs. Finally, page people on symptoms users feel, such as a fast SLO burn, not on causes such as high CPU. Every page should be urgent, actionable and real.
The full section covers OpenTelemetry's Collector, trace sampling and dashboards that answer questions. Next, in Section nine point seven, we look at capacity planning and performance testing. See you there.
What you need to know
- Monitoring checks predefined conditions; observability is the ability to explain new, unpredicted behaviour from existing telemetry, and must be designed in through consistent instrumentation and shared identifiers.
- Metrics are cheap aggregates for trends and alerts, structured logs record individual events, traces show one request's path through services, and profiles show where code spends resources.
- Counters, gauges and histograms are the core metric types; percentiles cannot be averaged across servers or time, but histogram buckets can be added and the percentile read from the merged result.
- RED (rate, errors, duration) describes services, USE (utilisation, saturation, errors) describes resources, and the four golden signals are latency, traffic, errors and saturation.
- Distributed tracing (Dapper, 2010) links spans through context propagation, standardised by the W3C traceparent header; OpenTelemetry, a CNCF project graduated in 2026, is the vendor-neutral standard for producing traces, metrics and logs.
- Unbounded label values explode metric cardinality and cost, so per-user or per-order detail belongs in traces and logs, and tail-based sampling keeps the traces that matter.
- Page on symptoms users feel, such as SLO burn rates, not on causes such as CPU, and build each dashboard to answer one question for one audience.
Monitoring and observability
Monitoring means collecting predefined measurements and checks and raising an alarm when they cross a threshold: disk 90% full, error rate above 1%, health check failing. It answers questions you knew to ask in advance. Observability is a property of the system: how well you can understand its internal state, including states nobody predicted, from the telemetry it already emits, without shipping new code to find out. The word comes from control theory, where Rudolf Kálmán defined it in 1960 as the ability to infer a system's internal state from its outputs.
The distinction matters because distributed systems mostly fail in new ways. A food-delivery order can be slow because one restaurant's tablet app retries aggressively, because one payment provider's endpoint in one region is degraded, or because a new menu feature makes one query slow only for restaurants with more than 500 items. No one wrote a dashboard for any of these. Monitoring tells you that checkout is slow; observability lets you ask for whom, where and why by slicing the telemetry by any attribute until the pattern appears.
Practitioners disagree about how far the distinction goes. One view, common among tool vendors, treats observability as an umbrella over three pillars: logs, metrics and traces. Charity Majors, Liz Fong-Jones and George Miranda (Observability Engineering, O'Reilly, 2022) argue that the pillars framing misses the point, and that observability requires rich, high-cardinality, structured events that can be queried in arbitrary combinations. Both camps agree on the architectural consequence: observability is a quality attribute that must be designed in, through consistent instrumentation, shared identifiers and context propagation, because it cannot be bolted on during an incident.
Kinds of telemetry
| Signal | What it is | Best at | Main cost or limit |
|---|---|---|---|
| Metrics | Numeric time series, aggregated per interval and labelled by a few dimensions | Cheap long-term trends, dashboards, alerting on SLIs | Pre-aggregated: cannot answer questions about individual requests; labels must be low-cardinality |
| Logs | Timestamped records of discrete events, ideally structured | Detail about one event: an error, a decision, an audit entry | Volume and cost grow with traffic; free text is hard to query |
| Traces | Trees of timed spans showing one request's path through services | Finding where time goes and which hop failed in a distributed call | Must be sampled at scale; useless if any hop drops the context |
| Profiles | Samples of where code spends CPU time or allocates memory | Explaining why one service is slow or expensive at the code level | Newer tooling; continuous profiling adds a little overhead |
| Wide events | One richly attributed record per request per service | Ad hoc slicing by any attribute (customer, version, region) | Needs a store built for high-cardinality queries |
Structured logging
A log line such as Payment failed for order 81F3 after 3001ms is easy to write and hard to use: every query needs a fragile regular expression, and every developer phrases things differently. Structured logging writes each entry as key-value fields, usually JSON, with agreed names: timestamp, level, service, event name, business identifiers (order, restaurant, courier), outcome, duration and the trace and span identifiers that link the line to its trace. Queries become precise (all card_authorisation_failed events for provider cardco in the last hour, grouped by http_status
), and logs from different services can be joined.
A few rules keep logs useful and affordable. Log events, not prose; use a fixed event name per kind of event. Use levels consistently, and do not log at error level for conditions nobody needs to act on. Never log secrets, tokens, full card numbers or unnecessary personal data; telemetry pipelines are copied, shipped to third parties and kept for months, so they are a common source of data leaks, and personal data in logs falls under the GDPR like any other copy (Section 8.7). Security logging and detection, a related but different purpose with its own retention and integrity needs, is covered in Section 8.8.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
version-trace id (16 bytes)-parent span id (8 bytes)-flags (01 = sampled)
{"ts": "2026-10-22T12:31:07.412Z", "level": "error",
"service": "payment-service", "event": "card_authorisation_failed",
"order_id": "ord_81F3", "provider": "cardco", "http_status": 504,
"duration_ms": 3001, "attempt": 2,
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736", "span_id": "00f067aa0ba902b7"}Metrics and why percentiles cannot be averaged
Most metric systems, such as Prometheus (a CNCF graduated project) and the OpenTelemetry metrics model, offer the same few types. A counter only increases, such as total requests served; you read it as a rate (requests per second over the last five minutes), which also survives restarts. A gauge is a current value that rises and falls: queue length, connections in use, memory. A histogram counts observations into buckets (for example requests under 50 ms, under 100 ms, under 250 ms and so on) plus a total count and sum, from which percentiles are estimated. Prometheus also has a summary type that computes quantiles inside each process.
The distinction between histograms and summaries is not a detail. As Section 2.4 noted, a percentile is a property of a whole distribution, and the percentile of a combined distribution cannot be computed from the percentiles of its parts. Averaging p99 values across servers, or across minutes, produces a number that is not a percentile of anything. Bucket counts, by contrast, can simply be added: sum each bucket across all servers, then estimate the percentile from the merged histogram. That is why fleet-wide latency SLIs should be built on histograms (or on counts of requests under a threshold, as in Section 9.5), never on per-instance summaries.
Worked example
Worked example: averaging two p99s
The menu service runs on two instances, each handling 10,000 requests in a five-minute interval. Instance A is healthy: all its requests take 20 to 300 ms, and its p99 is 180 ms. Instance B has a noisy neighbour: 7,000 requests take 20 to 300 ms, but 3,000 are spread evenly between 500 and 1,100 ms.
| B's p99 (its slowest 100 of 10,000) | 1,100 - 600 × (100 / 3,000) = 1,080 ms |
|---|---|
| Average of the two p99s | (180 + 1,080) / 2 = 630 ms |
| True p99 of all 20,000 requests | The slowest 200 are all on B: 1,100 - 600 × (200 / 3,000) = 1,060 ms |
| Error from averaging | 630 ms reported against 1,060 ms experienced: the dashboard understates the tail by 430 ms |
| With histograms | Add A's and B's bucket counts, then read the 99th percentile from the merged buckets: about 1,060 ms, limited only by bucket width |
Averaged percentiles can be wrong in either direction, and the error grows when instances carry different traffic.
The same applies over time: the p99 for an hour is not the average of twelve five-minute p99s.
What to measure: RED, USE and the golden signals
Three short checklists cover most of what a service should expose. The RED method, proposed by Tom Wilkie in 2015, applies to anything that serves requests: Rate (requests per second), Errors (failed requests per second) and Duration (the distribution of response times). The USE method, from Brendan Gregg, applies to resources: for every CPU, disk, network link, connection pool and queue, check Utilisation (how busy it is), Saturation (how much work is waiting) and Errors. The four golden signals from the Google SRE book's chapter on monitoring distributed systems are latency, traffic, errors and saturation: in effect RED plus saturation.
In practice, RED describes what users experience and USE explains why. If the payment service's duration rises (RED), USE on its resources might show a connection pool at 100% utilisation with 40 requests waiting (saturation), which in turn points to the slow card provider holding connections open. Measure latency separately for successes and failures: a fast error is not good latency, and a timeout is both an error and a slow response.
Three checklists compared
| Checklist | Applies to | Signals | Typical question it answers |
|---|---|---|---|
| RED (Wilkie) | Request-driven services | Rate, errors, duration | Is the order service serving users well right now? |
| USE (Gregg) | Resources: CPU, memory, disks, pools, queues | Utilisation, saturation, errors | Which resource is the bottleneck? |
| Four golden signals (SRE book) | User-facing systems | Latency, traffic, errors, saturation | Is the system healthy, and how close is it to its limits? |
Distributed tracing
In a monolith a stack trace shows where a slow request spent its time. Across services there is no single stack. Distributed tracing rebuilds one: every request gets a trace identifier at the edge, every unit of work becomes a span (with a name, start time, duration, status, attributes and the identifier of its parent span), and a tracing backend assembles the spans into a tree. Google's Dapper paper (Sigelman and colleagues, 2010) described the approach at scale: low overhead, instrumentation in shared libraries rather than in application code, and sampling so that only a fraction of requests is recorded.
Tracing depends entirely on context propagation: each service must pass the trace context to the next. The W3C Trace Context specification (a W3C Recommendation since 2020) standardises this for HTTP with a traceparent header holding a version, a 16-byte trace ID, the 8-byte ID of the calling span and flags such as whether the trace is sampled, plus an optional tracestate header for vendor data. For messaging, the same values travel in message headers, and the consumer's span refers back to the producer's. A single service that drops the header (an old library, a hand-written HTTP client, a batch job) breaks every trace that passes through it, which is why propagation belongs in shared platform libraries, not in each team's code.
Recording every span of every request is expensive at scale, so traces are sampled. Head-based sampling decides at the start of the request (keep 1 in 100) and is cheap, but it throws away most of the rare slow and failed requests you care about. Tail-based sampling buffers spans until the request finishes and then keeps all errors, all requests slower than a threshold and a small fraction of the rest; it needs more memory in the collector but keeps the interesting traces.
A trace across the food-delivery services
OpenTelemetry: one standard for instrumentation
Until about 2019, each tracing and monitoring vendor had its own agents and libraries, so changing vendor meant re-instrumenting every service. OpenTelemetry (OTel) was formed in 2019 by merging two earlier open-source projects, OpenTracing and OpenCensus, under the Cloud Native Computing Foundation (CNCF), and graduated as a CNCF project in May 2026. It provides language APIs and SDKs, automatic instrumentation for common frameworks and clients, the OTLP wire protocol, the Collector, and semantic conventions that fix attribute names such as http.request.method and http.response.status_code so that telemetry from different teams and languages lines up. Traces, metrics and logs are stable; profiles were added as a fourth signal and entered public alpha in 2026.
The architectural value is the separation of instrumentation from backend. Services emit OTLP to a Collector, usually running beside each node or as a central gateway; the Collector batches, samples, removes sensitive attributes, adds metadata such as the deployment version, and exports to whichever stores the organisation uses. Changing vendor, or sending the same data to two backends during a migration, becomes a Collector configuration change instead of a code change in two hundred services. A platform team typically provides the SDK setup, propagation and Collector as a paved road, so that product teams only add business attributes.
Telemetry pipeline
from opentelemetry import trace
import structlog
tracer = trace.get_tracer("order-service")
log = structlog.get_logger()
def place_order(order):
with tracer.start_as_current_span("place_order") as span:
span.set_attribute("restaurant.id", order.restaurant_id)
span.set_attribute("order.item_count", len(order.items))
quote = pricing.quote(order) # instrumented HTTP client adds
payment.authorise(order, quote) # the traceparent header itself
log.info("order_placed", order_id=order.id,
total_pence=quote.total_pence)
return order.idCardinality and cost
Telemetry can easily cost more than the system it observes. The main driver for metrics is cardinality: every distinct combination of label values is a separate time series that must be stored and indexed. Labels with a few values (HTTP method, status class, region) are fine. Labels with unbounded values (user ID, order ID, full URL with identifiers in it, error message text) multiply series without limit and can bring a metrics system down.
Worked example
Worked example: counting time series
The order service exports a request-duration histogram with 12 buckets. Prometheus stores each bucket as a series, plus a sum and a count, so 14 series per label combination.
| Route and method pairs | 60 |
|---|---|
| Status classes (1xx to 5xx) | 5 |
| Instances | 30 |
| Series for this one metric | 60 × 5 × 30 × 14 = 126,000 |
| Add a region label (5 regions) | 126,000 × 5 = 630,000 series |
| Add restaurant_id (40,000 restaurants) | 126,000 × 40,000 = about 5 billion potential series: unworkable |
Per-restaurant or per-customer questions belong in traces, logs or wide events, where high cardinality is expected, not in metric labels.
Exemplars, which attach a sample trace ID to a histogram bucket, link a latency spike on a cheap metric to an expensive, detailed trace.
Other cost levers are sampling traces (tail-based sampling keeps the useful ones), shorter retention for detailed data and longer retention for aggregates, dropping debug logs in production by default, and charging telemetry back to the teams that produce it so that its cost is visible. Treat telemetry volume as a design parameter with a budget, like any other resource.
Alerting on symptoms, not causes
The SRE book's guidance is that pages should be about symptoms users feel, not about the many possible causes. High CPU, a full disk on one replica or a restarted container are causes: often harmless, sometimes already handled by automation, and never a complete list of what can go wrong. A burning SLO, failing checkouts or a stalled courier-position pipeline are symptoms: they matter whatever their cause. Symptom-based alerts are fewer, rarely false and cover failures nobody predicted; cause-based signals belong on dashboards that help diagnose a symptom once it is paged.
Every page should be urgent, actionable and real. Alert fatigue is a reliability risk in itself: on-call engineers who are woken by meaningless alerts learn to dismiss them and miss the one that matters. A practical rule is to page on SLO burn rates (Section 9.5), send slower burns and resource warnings to a ticket queue, and link every page to a runbook and a dashboard.
Decision guide
Decision guide: should this alert page a human?
Apply these questions to every alert rule before it is allowed to wake someone, and again whenever it fires without anyone needing to act.
- Does the condition mean users are being hurt now, or will be soon (an SLO burning fast, a journey failing)?YesContinue to the next question.NoDo not page. Put it on a dashboard for diagnosis, or open a ticket if someone should look in working hours.
- Does it need a human to act, rather than an automatic response such as a restart or scale-out?YesContinue to the next question.NoAutomate the response and alert only if the automation fails.
- Must the action happen before the next working day?YesPage, and link the alert to a runbook and the relevant dashboard.NoSend it to the team's ticket queue instead of the pager.
- Has this alert paged in the last month without anyone needing to do anything?YesFix the threshold or the signal, or delete the alert: a page nobody acts on teaches people to ignore pages.NoKeep it, and review it at the next on-call handover.
Dashboards that answer questions
A wall of fifty graphs answers no question quickly. Good dashboards are designed like any user interface: for a named audience and a specific question. A service overview answers is this service healthy?
with its SLOs, remaining error budget and RED metrics at the top, and the USE metrics of its key resources below. A journey dashboard for the on-sale answers are fans getting tickets?
with admissions, seat holds, payments and failures per minute end to end. Diagnostic dashboards are reached by drilling down, not shown first.
Keep dashboards in version control as code, give each an owner, show deployments and feature-flag changes as annotations on the graphs (most incidents follow a change), and delete dashboards nobody has opened for months. During an incident, the first useful view is usually the user-facing journey, the second the trace of a failing request, and only then the resource metrics.
Check your understanding
Why does the p99 for a whole fleet have to be computed from merged histograms rather than from each instance's p99?
Because a percentile of combined data is not a function of the parts' percentiles; bucket counts add exactly, so the merged histogram gives a true (bucket-limited) estimate, while averaged p99s can be badly wrong.
What breaks if one service in a call chain does not forward the traceparent header?
Every trace that passes through it is split: spans after that hop start a new trace, so the end-to-end picture and the link between cause and symptom are lost.
Why is order_id a poor metric label but a good span attribute?
As a label it creates a new time series per order, exploding cardinality and cost; as a span or log attribute it is stored once per event, where high cardinality is expected and useful for queries.
Which of these should page someone: CPU at 95% on one instance, or checkout SLO burning at rate 20?
The SLO burn, because it is a symptom users feel; the CPU reading is a possible cause that belongs on a diagnostic dashboard.
Sources and further reading
Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.
Ask: “We have logs from twenty services but cannot tell why some orders are slow. What should we add first: tracing, better logs or more metrics?”