Observability
Purpose
Instrument a system so that a question you did not anticipate can still be answered from its telemetry. Monitoring tells you that something is wrong; observability lets you find out why without shipping new code.
When to Use
- Instrumenting a new service.
- An incident where the data needed to diagnose it did not exist.
- Defining SLOs and the alerts that derive from them.
- Reducing alert noise that the team has learned to ignore.
Capabilities
- Structured logging with consistent, queryable fields.
- Metrics: RED (rate, errors, duration) for services, USE (utilization, saturation, errors) for resources.
- Distributed tracing with OpenTelemetry and context propagation.
- SLO and error-budget definition.
- Alert design: symptom-based, actionable, and rare.
Inputs
- The service, its dependencies, and its user-facing operations.
- The questions you have needed to answer in past incidents.
- Existing telemetry and its gaps.
Outputs
- Structured logs with a trace ID on every line.
- RED metrics per endpoint and USE metrics per resource.
- SLOs with error budgets, and alerts that fire on symptoms.
Workflow
- Instrument the user-visible path first — Rate, errors, and duration per endpoint. This answers "is it broken and for whom", which is the first question in every incident.
- Log structurally — JSON with consistent field names, including a trace ID. A log line that must be parsed with a regex is a log line nobody will query at 3am.
- Propagate context — OpenTelemetry, with the trace ID flowing through every hop, including the message queue. A trace that stops at a queue boundary is half a trace.
- Define SLOs from the user's perspective — "99.9% of checkout requests succeed within 500ms." Not "CPU stays below 80%", which no user has ever cared about.
- Alert on symptoms, not causes — Page on "error budget burning fast", not on "CPU high". High CPU with a healthy service is not an incident.
- Delete the alerts nobody acts on — An alert that has fired forty times and been acknowledged forty times without action is training the team to ignore alerts.
Best Practices
- Every alert that pages a human must be urgent, actionable, and real. If it is not all three, it belongs on a dashboard, not on a pager.
- High-cardinality fields (user ID, request ID) belong in traces and logs, not in metric labels. A metric label with a million values will take down your metrics backend.
- The trace ID must appear in the log line, the error report, and the response header. That is what turns three tools into one investigation.
- Log at the boundaries: request in, request out, and the decision points between. Logging every function entry produces volume, not insight.
- Sample traces, but always keep the errors and the slow ones. Head-based sampling that drops the 1% of requests that failed has thrown away the only useful data.
- An SLO with no error budget policy is a number on a dashboard. Decide in advance what happens when the budget is exhausted.
Examples
A structured log line that is actually useful:
{
"timestamp": "2026-03-04T09:14:22.481Z",
"level": "error",
"message": "payment authorization failed",
"service": "checkout-api",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"order_id": "ord_01HXKZ",
"tenant_id": "tnt_9f2",
"gateway": "stripe",
"gateway_error_code": "card_declined",
"duration_ms": 842,
"attempt": 2
}
Every field is queryable. "Show me declines for tenant tnt_9f2 in the last hour, grouped by gateway error code" is one query, not a grep.
A symptom-based alert with a burn rate:
# Pages only when the error budget is being consumed fast enough to matter.
- alert: CheckoutErrorBudgetBurn
expr: |
(
sum(rate(http_requests_total{route="/checkout", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{route="/checkout"}[5m]))
) > (14.4 * 0.001) # 14.4x burn against a 99.9% SLO
for: 2m
labels: { severity: page }
annotations:
summary: "Checkout is burning its error budget 14x faster than sustainable"
runbook: "https://runbooks.example.com/checkout-errors"
dashboard: "https://grafana.example.com/d/checkout"
A 14.4x burn rate over 5 minutes exhausts a 30-day budget in about two days. That is worth waking someone for. A 2x burn is not.
Notes
- Multi-window, multi-burn-rate alerting (a fast window for severe burns, a slow window for gradual ones) is the standard way to get both fast detection and low noise. A single threshold gives you one or the other.
- OpenTelemetry is now the vendor-neutral standard for traces and metrics. Instrumenting with a vendor SDK locks the instrumentation to the vendor; instrumenting with OTel does not.
- Logs are the most expensive telemetry per byte and the least structured. If a question can be answered by a metric or a trace, do not answer it with a log search.
1---2name: observability3description: Use when instrumenting a system or when an incident cannot be diagnosed from existing telemetry. Covers structured logging, metrics, distributed tracing, SLOs, and alerts that are worth waking someone for.4---56# Observability78## Purpose910Instrument a system so that a question you did not anticipate can still be answered from its telemetry. Monitoring tells you *that* something is wrong; observability lets you find out *why* without shipping new code.1112## When to Use1314- Instrumenting a new service.15- An incident where the data needed to diagnose it did not exist.16- Defining SLOs and the alerts that derive from them.17- Reducing alert noise that the team has learned to ignore.1819## Capabilities2021- Structured logging with consistent, queryable fields.22- Metrics: RED (rate, errors, duration) for services, USE (utilization, saturation, errors) for resources.23- Distributed tracing with OpenTelemetry and context propagation.24- SLO and error-budget definition.25- Alert design: symptom-based, actionable, and rare.2627## Inputs2829- The service, its dependencies, and its user-facing operations.30- The questions you have needed to answer in past incidents.31- Existing telemetry and its gaps.3233## Outputs3435- Structured logs with a trace ID on every line.36- RED metrics per endpoint and USE metrics per resource.37- SLOs with error budgets, and alerts that fire on symptoms.3839## Workflow40411. **Instrument the user-visible path first** — Rate, errors, and duration per endpoint. This answers "is it broken and for whom", which is the first question in every incident.422. **Log structurally** — JSON with consistent field names, including a trace ID. A log line that must be parsed with a regex is a log line nobody will query at 3am.433. **Propagate context** — OpenTelemetry, with the trace ID flowing through every hop, including the message queue. A trace that stops at a queue boundary is half a trace.444. **Define SLOs from the user's perspective** — "99.9% of checkout requests succeed within 500ms." Not "CPU stays below 80%", which no user has ever cared about.455. **Alert on symptoms, not causes** — Page on "error budget burning fast", not on "CPU high". High CPU with a healthy service is not an incident.466. **Delete the alerts nobody acts on** — An alert that has fired forty times and been acknowledged forty times without action is training the team to ignore alerts.4748## Best Practices4950- Every alert that pages a human must be urgent, actionable, and real. If it is not all three, it belongs on a dashboard, not on a pager.51- High-cardinality fields (user ID, request ID) belong in traces and logs, not in metric labels. A metric label with a million values will take down your metrics backend.52- The trace ID must appear in the log line, the error report, and the response header. That is what turns three tools into one investigation.53- Log at the boundaries: request in, request out, and the decision points between. Logging every function entry produces volume, not insight.54- Sample traces, but always keep the errors and the slow ones. Head-based sampling that drops the 1% of requests that failed has thrown away the only useful data.55- An SLO with no error budget policy is a number on a dashboard. Decide in advance what happens when the budget is exhausted.5657## Examples5859**A structured log line that is actually useful:**6061```json62{63 "timestamp": "2026-03-04T09:14:22.481Z",64 "level": "error",65 "message": "payment authorization failed",66 "service": "checkout-api",67 "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",68 "span_id": "00f067aa0ba902b7",69 "order_id": "ord_01HXKZ",70 "tenant_id": "tnt_9f2",71 "gateway": "stripe",72 "gateway_error_code": "card_declined",73 "duration_ms": 842,74 "attempt": 275}76```7778Every field is queryable. "Show me declines for tenant tnt_9f2 in the last hour, grouped by gateway error code" is one query, not a grep.7980**A symptom-based alert with a burn rate:**8182```yaml83# Pages only when the error budget is being consumed fast enough to matter.84- alert: CheckoutErrorBudgetBurn85 expr: |86 (87 sum(rate(http_requests_total{route="/checkout", status=~"5.."}[5m]))88 / sum(rate(http_requests_total{route="/checkout"}[5m]))89 ) > (14.4 * 0.001) # 14.4x burn against a 99.9% SLO90 for: 2m91 labels: { severity: page }92 annotations:93 summary: "Checkout is burning its error budget 14x faster than sustainable"94 runbook: "https://runbooks.example.com/checkout-errors"95 dashboard: "https://grafana.example.com/d/checkout"96```9798A 14.4x burn rate over 5 minutes exhausts a 30-day budget in about two days. That is worth waking someone for. A 2x burn is not.99100## Notes101102- Multi-window, multi-burn-rate alerting (a fast window for severe burns, a slow window for gradual ones) is the standard way to get both fast detection and low noise. A single threshold gives you one or the other.103- OpenTelemetry is now the vendor-neutral standard for traces and metrics. Instrumenting with a vendor SDK locks the instrumentation to the vendor; instrumenting with OTel does not.104- Logs are the most expensive telemetry per byte and the least structured. If a question can be answered by a metric or a trace, do not answer it with a log search.