Observability#

Observability is the ability to understand the internal state of a system by examining its external outputs. In distributed systems, you cannot debug by attaching a debugger — you need telemetry: metrics, logs, and traces working together to answer “why is this broken?”


Observability vs Monitoring#

AspectMonitoringObservability
ApproachPredefined checks against known failuresExplore unknown failures dynamically
Questions“Is the system up?” (known-knowns)“Why is it slow for user X?” (unknown-unknowns)
DataPredefined metrics and thresholdsHigh-cardinality, high-dimensionality data
AlertsThreshold-based (CPU > 80%)SLO burn-rate based
Debug flowDashboard → runbook → fixQuery telemetry → form hypothesis → verify

Monitoring tells you something is wrong. Observability helps you understand why.


Three Pillars of Observability#

graph TD
    O[Observability] --> M[Metrics]
    O --> L[Logs]
    O --> T[Traces]
    M --> |Aggregated numbers| Q1[What is happening?]
    L --> |Individual events| Q2[What happened specifically?]
    T --> |Request flow| Q3[Where did time go?]

How They Work Together#

  1. Alert fires (metric: error rate > 1%) → something is wrong
  2. Dashboard (metrics) → which service? which endpoint?
  3. Traces → find slow/failing requests, see which service in the chain failed
  4. Logs → read the exact error message, stack trace, context

Metrics#

Metrics are aggregated numerical measurements over time. They answer “how much” and “how fast” questions cheaply.

Metric Types#

TypeDescriptionExample
CounterMonotonically increasing valueTotal requests served, errors count
GaugeValue that goes up and downCurrent memory usage, queue depth
HistogramDistribution of values in bucketsRequest duration (p50, p95, p99)
SummaryClient-calculated quantilesSimilar to histogram, pre-computed

Counter vs Gauge#

Counter: 0 → 1 → 2 → 3 → 4 → 5 → 6  (always increasing, reset on restart)
Gauge:   45 → 62 → 38 → 71 → 55 → 40  (fluctuates)

Counters track cumulative totals (derive rate with rate() function). Gauges track current state.

Histogram: Understanding Percentiles#

A histogram tracks the distribution of values (e.g., request latency):

PercentileMeaning
p50 (median)50% of requests are faster than this
p9090% of requests are faster (1 in 10 is slower)
p9595% of requests are faster (1 in 20 is slower)
p9999% of requests are faster (1 in 100 is slower)

Why p99 matters more than average: If average latency is 100ms but p99 is 5000ms, 1% of your users experience 50x worse performance. At scale (1M requests/day), that’s 10,000 terrible experiences daily.

RED Method (for request-driven services)#

SignalWhat to Measure
RateRequests per second
ErrorsFailed requests per second
DurationLatency distribution (p50, p95, p99)

USE Method (for resources)#

SignalWhat to Measure
Utilisation% of resource being used (CPU: 72%)
SaturationWork queued because resource is full
ErrorsError events on the resource

Structured Logging#

Structured logs use machine-parseable formats (JSON) instead of free-text strings, enabling powerful querying and aggregation.

Unstructured vs Structured#

# Unstructured (hard to query)
2024-03-15 10:23:45 ERROR Failed to process order 12345 for user abc: timeout

# Structured (easy to query)
{
  "timestamp": "2024-03-15T10:23:45.123Z",
  "level": "ERROR",
  "message": "Failed to process order",
  "service": "order-processor",
  "order_id": "12345",
  "user_id": "abc",
  "error": "timeout",
  "duration_ms": 30000,
  "trace_id": "4bf92f3577b34da6"
}

Structured Logging Best Practices#

PracticeWhy
Use JSON formatParseable by log aggregators
Include trace_idCorrelate logs with traces
Include request_idGroup logs for one request
Use consistent field namesEnable cross-service queries
Log at appropriate levelsDEBUG/INFO/WARN/ERROR/FATAL
Never log PII/secretsCompliance and security
Include context (service, version)Quick identification

Log Levels#

LevelUse When
DEBUGDevelopment-time detail (disable in prod)
INFONormal operations worth noting (request started, job completed)
WARNSomething unexpected but handled (retry succeeded, fallback used)
ERROROperation failed, needs attention (API call failed, data corruption)
FATALSystem cannot continue (config missing, port in use)

Distributed Tracing#

A trace follows a single request across multiple services, showing the complete path and timing.

Trace Structure#

graph TD
    subgraph "Trace (single request)"
        A[Span: API Gateway - 250ms] --> B[Span: Auth Service - 20ms]
        A --> C[Span: Order Service - 180ms]
        C --> D[Span: Payment Service - 120ms]
        C --> E[Span: Inventory Service - 40ms]
    end

Trace = collection of spans representing one end-to-end request Span = one operation within the trace (has start time, duration, service name, status) Trace ID = unique identifier propagated across all services in the request

Trace ID Propagation#

The trace ID must be passed between services on every call:

# HTTP header propagation
headers = {
    "traceparent": f"00-{trace_id}-{span_id}-01",
    # W3C Trace Context standard
}
response = requests.get(downstream_url, headers=headers)
Propagation MethodProtocol
HTTP header (traceparent)REST, gRPC-Web
gRPC metadatagRPC
Message attributeKafka, SQS
Baggage headerCross-cutting context

What Tracing Reveals#

QuestionHow Tracing Answers It
Why is this request slow?See which span took the longest
Which service failed?Span with error status
What’s the critical path?Longest sequential chain
Are calls parallel or serial?Span timing overlap
How deep is the call chain?Span depth count

Alerting Best Practices#

Alert on Symptoms, Not Causes#

Bad (cause-based)Good (symptom-based)
CPU > 80%Error rate > 1%
Disk > 90%Latency p99 > 2s
Pod restartedSuccess rate < 99.5%

Users don’t care about CPU — they care about errors and latency.

Alert Severity#

SeverityResponseExample
Critical / PageWake someone upPayment processing down
HighRespond within 1 hourError rate elevated
MediumRespond within business hoursDisk filling slowly
Low / InfoReview next sprintDependency deprecated

Reducing Alert Fatigue#

  • Alert on SLO burn rates, not raw thresholds
  • Require alerts to be actionable (if no action exists, it’s not an alert)
  • Group related alerts (don’t page 50 times for one incident)
  • Auto-resolve when condition clears
  • Review and tune alerts quarterly (delete noisy ones)

SLOs, SLIs, and Error Budgets#

Definitions#

TermDefinitionExample
SLI (Service Level Indicator)The metric you measure% of requests < 300ms
SLO (Service Level Objective)Target value for the SLI99.9% of requests < 300ms
SLA (Service Level Agreement)Contractual commitment + consequences99.9% uptime or credits issued
Error BudgetAllowed failure = 1 - SLO0.1% = 43 minutes/month

Error Budget Concept#

If your SLO is 99.9% availability (43 min downtime/month allowed):

Error budget = 100% - 99.9% = 0.1%
Monthly budget = 30 days × 24h × 60min × 0.001 = 43.2 minutes

While you have budget remaining → deploy features, take risks. When budget is exhausted → freeze deployments, focus on reliability.

Choosing SLOs#

Service TypeTypical SLO
Payment processing99.99% success rate
User-facing API99.9% availability, p99 < 500ms
Internal batch jobs99% completion within window
Static content / CDN99.99% availability
Dev/staging environments99% (lower priority)

Dashboards#

Dashboard Design Principles#

PrincipleDescription
Top-downStart with high-level health, drill down to detail
USE/RED focusedOrganise around standard methodologies
Time-alignedAll graphs share the same time axis
ContextShow SLO targets as reference lines
ActionableEvery panel should help answer “what do I do?”

Standard Dashboard Layout#

  1. Top row: SLO status (burning? healthy?), error budget remaining
  2. Second row: Rate, Errors, Duration (RED) for the service
  3. Third row: Resource utilisation (CPU, memory, network)
  4. Bottom: Dependency health (downstream service latency/errors)

Tooling Overview#

ToolCategoryStrengths
PrometheusMetricsPull-based, PromQL, Kubernetes-native
GrafanaDashboardsMulti-datasource, flexible, open-source
DatadogFull platformMetrics + logs + traces + APM unified
JaegerTracingOpen-source distributed tracing
OpenTelemetryInstrumentationVendor-neutral SDK for metrics/logs/traces
ELK StackLogsElasticsearch + Logstash + Kibana
LokiLogsLightweight, Grafana-native, label-based
PagerDutyAlertingIncident routing, escalation, on-call

OpenTelemetry (OTel)#

OpenTelemetry is the industry standard for instrumentation. It provides SDKs that generate traces, metrics, and logs in a vendor-neutral format, exportable to any backend.

from opentelemetry import trace

tracer = trace.get_tracer("order-service")

with tracer.start_as_current_span("process_order") as span:
    span.set_attribute("order.id", order_id)
    span.set_attribute("order.total", total_amount)
    # ... business logic

Runbooks#

A runbook documents how to respond to a specific alert or incident. Every alert should link to a runbook.

Runbook Template#

## Alert: High Error Rate on Payment Service

### Severity: Critical (pages on-call)

### Symptoms
- Error rate > 2% for 5 minutes
- Users seeing "payment failed" messages

### Likely Causes
1. Payment gateway outage
2. Database connection pool exhausted
3. Bad deployment (check recent deploys)

### Diagnosis Steps
1. Check payment gateway status page: [link]
2. Query: `sum(rate(http_errors[5m])) by (endpoint)`
3. Check recent deployments: [link to CI/CD]
4. Verify DB connections: [dashboard link]

### Remediation
- If gateway outage: Enable fallback processor [runbook link]
- If DB connections: Restart service pods
- If bad deploy: Rollback [procedure link]

### Escalation
- After 15 min: Page payments team lead
- After 30 min: Page engineering director

Runbook Best Practices#

  • Written by the team that owns the service
  • Linked from every alert (clickable URL in alert message)
  • Updated after every incident (if the runbook was wrong or incomplete)
  • Tested periodically (game days)
  • Include copy-pasteable commands (no ambiguity under pressure)

Key Takeaways#

  • Observability enables understanding unknown failures — monitoring alone only catches known failure modes
  • Metrics, logs, and traces are complementary — metrics detect, traces locate, logs explain
  • Structured logging transforms logs from unreadable text into queryable data — always use JSON
  • Distributed tracing is essential in microservices — without it, debugging cross-service issues is guesswork
  • Alert on symptoms (errors, latency), not causes (CPU, disk) — users experience symptoms
  • SLOs and error budgets balance reliability with feature velocity — they make trade-offs explicit
  • Every alert must be actionable and link to a runbook — if you can’t act on it, delete it
  • OpenTelemetry is the standard instrumentation layer — instrument once, export to any backend