Observability: Ask Production Better Questions

Dashboards do not make a system observable. You need enough evidence to explain a new failure without deploying new debugging code. Start with user-visible symptoms, then connect logs, metrics, and traces through stable context.

🎙️ Published & recorded: ·

01Observability starts with a question

Monitoring asks whether known bad conditions are happening. Observability helps you investigate conditions you did not predict. The useful test is simple: when one customer says checkout hung at ten fourteen, can you find that request, see each dependency it called, and explain where the time went?

User symptom: checkout took 18 seconds

Questions:
Which requests were affected?
Did errors or latency change by region or release?
Which operation consumed the time?
Which dependency returned slowly or failed?
Was the user impact inside the reliability objective?
What changed just before the symptom?

Collect telemetry to answer operational questions, not because three signal types appear in a vendor diagram. A metric reveals a population-level spike. A trace locates slow work in representative requests. A log preserves detailed events and errors. Their shared service, environment, release, route, and trace identifiers turn separate records into evidence.

My ruleIf nobody has used a dashboard for a decision in ninety days, delete or redesign it. A wall of green charts is decoration when it cannot distinguish a broken checkout from a healthy home page.

02Logs should record events, not prose

A useful log event has a stable name, timestamp, severity, service, environment, release, request or trace identifier, outcome, duration, and bounded business context. Write structured JSON so machines can filter fields without parsing sentences. Keep the human message, but do not make it the schema.

{
  "timestamp": "2026-07-25T10:14:32.481Z",
  "level": "error",
  "event": "payment_authorization_failed",
  "service": "checkout",
  "release": "2026.07.25.3",
  "trace_id": "4f92c6...",
  "order_id": "ord_8132",
  "provider": "acquirer_a",
  "duration_ms": 3002,
  "error_type": "deadline_exceeded"
}

Log once at the boundary that can add useful context. If four layers catch and rethrow the same exception, four identical stack traces raise cost without adding evidence. Preserve the original cause when wrapping errors. Do not log passwords, session cookies, authorization headers, access tokens, full payment data, or unreviewed request bodies.

Bad log text from real systemsSomething went wrong contains no operation, target, outcome, identifier, or cause. Replace it with an event such as invoice_export_failed, the safe invoice identifier, dependency, duration, error type, and trace ID. Keep the raw exception in a controlled field, with redaction applied before export.

03Metrics summarize behavior over time

Counters only increase and suit requests, failures, and jobs. Gauges move up and down and suit queue depth or memory in use. Histograms count observations in buckets and let you aggregate latency across instances. Use rates for counters. A raw cumulative request count mostly tells you how long the process has been alive.

http_requests_total{service="checkout",route="/orders/{id}",status_class="5xx"}
http_request_duration_seconds_bucket{service="checkout",route="/orders/{id}",le="0.5"}
queue_depth{service="fulfillment",queue="shipments"}
build_info{service="checkout",release="2026.07.25.3"} 1

# Useful views
error rate = 5xx request rate / all request rate
p95 latency from histogram buckets
queue age alongside queue depth

Averages hide the users waiting in the tail. Report a latency distribution and inspect the ninety-fifth or ninety-ninth percentile when the traffic volume supports it. Choose histogram boundaries around product thresholds, not round numbers inherited from another service. A thirty second job and a fifty millisecond API need different buckets.

Name the unitPut seconds, bytes, or totals in metric names. Record status class or a bounded outcome, not the full error message. Emit a release marker so a graph can answer the first incident question: what changed?

04Traces show one request crossing boundaries

A trace represents one end-to-end operation. Spans represent timed pieces of work, with parent-child relationships, attributes, events, and status. The incoming server span should contain child spans for database, cache, queue, and outbound HTTP calls. The shape shows where time accumulated and where an error first appeared.

POST /checkout                         3.24 s
├─ validate basket                     0.02 s
├─ SELECT inventory                    0.08 s
├─ POST payment-provider /authorize    3.01 s  ERROR
└─ publish order-failed                0.03 s

trace_id: shared by every span
span_id: unique operation
parent_span_id: causal relationship
baggage: propagated context; use sparingly

Context propagation is the part teams underestimate. Forward the standard trace context through HTTP and messaging libraries. For async work, link the consumer span to the producer context. Do not invent a trace ID header if your instrumentation already supports a standard one. Never place secrets or unbounded payloads in span attributes.

Sampling changes what you can proveOne percent head sampling can discard the rare failed request before its outcome is known. Keep all errors when possible, use tail sampling where the architecture supports it, and retain enough normal traffic for comparison. Traces are evidence with a sampling policy, not a complete transaction ledger.

05Instrument boundaries before internals

Start where work enters and leaves the service. Instrument HTTP servers and clients, database queries, cache calls, message producers and consumers, scheduled jobs, and external providers. These boundaries reveal latency, errors, retries, and fan-out. Adding a span around every helper function creates noise and overhead without explaining the system.

Minimum service telemetry:
request count, outcome, and duration
in-flight work and queue age
dependency count, outcome, and duration
retries and timeouts
resource saturation
release and configuration version

Useful span attributes:
service.name, deployment.environment, service.version
http.request.method, http.route, http.response.status_code
db.system, db.operation.name
messaging.system, messaging.destination.name

Prefer automatic instrumentation for standard libraries, then add manual spans around business operations such as price calculation or payment authorization. Review generated route names and database statements before shipping. Parameterized route templates are safe dimensions; raw URLs and SQL values can expose data and explode cardinality.

Verify the pipelineCreate one synthetic request with a known marker in a test environment. Confirm its metric increment, structured log, server span, child dependency span, release attribute, and trace-to-log link. Then stop the telemetry collector and verify the application keeps serving. An observability pipeline must not become a new critical dependency.

06SLOs turn reliability into a budget

A service level indicator measures user-visible success, such as the proportion of checkout requests that complete correctly within two seconds. A service level objective sets the target over a window. The error budget is the allowed unsuccessful fraction. It supports an explicit trade: spend reliability on change, but slow down when failures consume the budget too quickly.

SLI = good checkout requests / eligible checkout requests
SLO = 99.9% over a rolling 28-day window

Allowed bad fraction = 0.1%
At 10,000,000 eligible requests:
error budget = 10,000 bad requests

Burn rate = observed bad rate / allowed bad rate
burn rate 1  → budget consumed exactly across the window
burn rate 14 → budget disappearing fourteen times too fast

Define eligible and good precisely. Exclude health checks and requests rejected before entering the product flow. Decide how cancellations and provider failures count from the user's perspective, not from team ownership. A dependency-caused failed checkout is still a failed checkout.

Do not choose five nines for statusA target should follow user need and achievable architecture. Ninety-nine point nine percent allows about forty minutes of bad time in a twenty-eight day window if failure is continuous, though request-based objectives are better for uneven traffic. A stricter target without engineering investment creates dishonest reports, not reliability.

07Page on user impact, ticket on causes

A page should mean a person must act now. Alert on fast error-budget burn, sustained user-visible latency, or a backlog that will soon miss its deadline. High CPU, one crashed replica, or low disk may matter, but they are usually cause signals. Page on them only when immediate action prevents known impact.

PAGE:
Checkout SLO burn rate is 18 over 5 minutes
and 7 over 1 hour, affecting 3 regions.
Runbook: /runbooks/checkout-slo
Dashboard: /d/checkout
Recent releases: 2026.07.25.3 at 10:08 UTC

TICKET:
Search cluster disk projected to reach 80% in 4 days.
Owner: search-platform
Capacity procedure: /runbooks/search-capacity

Use a short and a long window together. The short window catches a severe outage quickly; the long window prevents paging on a one-minute blip. Every alert needs an owner, impact statement, current evidence, and first diagnostic action. If the receiver cannot act, route it elsewhere or remove it.

Alert fatigue is a correctness bugIf an alert pages weekly and engineers acknowledge it without action, it is teaching them to ignore the system. Fix the service, adjust a bad threshold with evidence, downgrade it, or delete it. Do not celebrate a low mean time to acknowledge for alarms nobody believes.

08Cardinality is the telemetry bill hiding in labels

Each unique metric label combination creates a time series. A label with user IDs, request IDs, raw URLs, stack traces, or timestamps can create millions of series and overload storage or query systems. Metrics need bounded dimensions. Put high-cardinality identifiers in sampled traces or indexed logs where you can query them deliberately.

# Bad: one series per user and raw path
http_requests_total{user_id="813294",path="/orders/998123"}

# Better: bounded route and status class
http_requests_total{route="/orders/{id}",status_class="2xx"}

# Estimate series before shipping
services × routes × methods × status_classes × regions
40 × 80 × 5 × 5 × 4 = 320,000 possible series
Contain a cardinality incidentOne: identify the metric and label creating new series. Two: stop or relabel that dimension at the collector if the backend is unstable. Three: deploy a bounded route or error type. Four: remove or expire the toxic series according to backend procedure. Five: add a series-budget check and alert on creation rate. Buying more storage before removing request_id from labels funds the bug.

Control cost with retention tiers, sampling, aggregation, and ownership. Keep detailed telemetry where it answers active questions. Downsample older metrics. Retain security and audit records according to their separate policy. “Store everything forever” is not an observability strategy.

09Debug one incident from symptom to cause

Suppose checkout latency jumps after a release. Start with impact, not random host inspection. Confirm affected routes, regions, releases, and time range. Compare successful and failed traces. In the slow traces, the payment span runs for three seconds and ends with a deadline error. Now inspect dependency outcomes and retry count.

rpc error: code = DeadlineExceeded desc = context deadline exceeded

10:08  release 2026.07.25.3 begins
10:11  checkout p95: 0.8 s → 6.4 s
10:11  payment attempts/request: 1.0 → 2.8
10:11  payment deadline errors increase

Trace evidence:
attempt 1: 3.0 s deadline
attempt 2: 3.0 s deadline
request deadline: 6.5 s
Fix it in orderOne: stop the impact by rolling back release 2026.07.25.3 or disabling its retry flag. Two: verify latency, error rate, and budget burn recover. Three: compare release configuration; the new code retried timeout failures inside the same request deadline. Four: give each attempt a bounded budget, retry only safe transient failures, and add jitter. Five: load-test a slow provider and assert total request time. Six: add attempts-per-request telemetry and annotate future releases.

The raw text tells you the local operation exceeded its context deadline. It does not prove the provider was down. The trace proves two sequential waits. The release marker and rollback prove the regression follows the new retry behavior. Good incident work separates observation, hypothesis, test, and conclusion.

10Observability build and incident cheat sheet

Build from the user journey inward. One carefully instrumented checkout path is worth more than host dashboards across fifty services when nobody can connect those hosts to a failed order.

BUILD
1. Name critical user journeys and owners
2. Define good events and an SLO for each journey
3. Measure rate, errors, duration, and saturation
4. Instrument inbound, outbound, database, cache, and queue boundaries
5. Propagate trace context across HTTP and messaging
6. Add structured events with safe identifiers and release version
7. Link metrics to traces and traces to logs
8. Set retention, sampling, redaction, and series budgets
9. Test telemetry loss; the product must keep working
10. Page on fast user-impact burn with a usable runbook

INCIDENT
1. State user impact and exact time window
2. Check SLI, traffic, regions, routes, and release markers
3. Compare healthy and unhealthy populations
4. Open representative traces; locate the slow or failed span
5. Query correlated logs by trace ID and event
6. Check dependency, retry, queue, and saturation metrics
7. Mitigate with rollback, flag, traffic shift, or capacity
8. Verify user metrics recover
9. Preserve timestamps, queries, traces, and decisions
10. Fix the missing test and missing signal

The useful standard is not perfect telemetry. It is a short path from a user symptom to a defensible explanation. Keep identifiers connected, dimensions bounded, alerts actionable, and release changes visible. Then production can answer questions instead of merely producing charts.

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.