Logging, Monitoring, and Observability
Logging, Monitoring, and Observability
Observability answers: "Why is it slow / failing / behaving oddly right now?" Logging alone tells stories after the fact; monitoring warns you; true observability lets you ask new, ad‑hoc questions without adding more code first.
1. The Three Pillars (and Their Roles)
| Pillar | Data Shape | Typical Storage | Primary Questions |
|---|---|---|---|
| Logs | Event lines (text / JSON) | Log index (e.g. Loki, Elasticsearch) | What happened? Context + errors |
| Metrics | Numeric time series | TSDB (Prometheus) | Is behavior within normal bounds? |
| Traces | Spans linked by IDs | Trace store (Jaeger/Tempo) | Where is the latency/error occurring across services? |
Modern practice: Use structured JSON logs, Prometheus metrics, and OpenTelemetry traces emitted from the same code paths sharing correlation IDs.
2. Request Correlation
Generate a unique trace_id (or reuse from reverse proxy) at request entry, then propagate via headers (e.g., traceparent / x-request-id). Include that ID in every log line and span.
sequenceDiagram
participant C as Client
participant GW as API Gateway
participant S1 as Service A
participant S2 as Service B
C->>GW: HTTP Request
GW->>S1: Adds trace_id
S1->>S2: Propagates trace_id
S2-->>S1: Response (trace annotated)
S1-->>GW: Aggregated response
GW-->>C: Returns trace_id header
With a trace ID the frontend or support team can paste it into dashboards to retrieve logs, metrics overlays, and the distributed trace visualization quickly.
3. Logging Guidelines
| Aspect | Practice | Example |
|---|---|---|
| Format | JSON, one object per line | { "level":"info", "msg":"user login", "user_id":42 } |
| Levels | Use debug < info < warn < error |
Limit error to actionable failures |
| Context | Key/value pairs over string concat | {"order_id":123,"duration_ms":87} |
| PII | Avoid raw emails, tokens | Hash or truncate if needed |
| Volume | Rate-limit noisy debug | Sample high-frequency events |
Bad: print("User 42 failed login again!!!")
Better:
{"ts":"2025-01-12T15:04:05Z","level":"warn","event":"auth.login.failed","user_id":42,"ip":"203.0.113.5"}
Python Example (Structure)
import json, sys, time, uuid
def log(level, **fields):
record = {
"ts": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
"level": level,
**fields
}
sys.stdout.write(json.dumps(record) + "\n")
trace_id = str(uuid.uuid4())
log("info", event="order.create.start", trace_id=trace_id, order_id=101)
try:
# ... business logic ...
log("info", event="order.create.ok", trace_id=trace_id, order_id=101, duration_ms=53)
except Exception as e:
log("error", event="order.create.error", trace_id=trace_id, order_id=101, error=str(e))
4. Metrics Fundamentals
| Type | Use | Example |
|---|---|---|
| Counter | Ever-increasing count | http_requests_total |
| Gauge | Arbitrary up/down value | queue_depth |
| Histogram | Distribution (latency, sizes) | request_latency_seconds_bucket |
| Summary | Client-side quantiles (avoid on Prometheus server) | Not widely recommended for backend latency |
Expose metrics endpoint (e.g., /metrics) and scrape with Prometheus.
Example (Python + Prometheus client)
from prometheus_client import Counter, Histogram, start_http_server
import time, random
REQS = Counter('api_requests_total', 'Total API requests', ['route'])
LAT = Histogram('api_request_latency_seconds', 'Latency', ['route'])
def handle(route):
with LAT.labels(route).time():
time.sleep(random.random()/20)
REQS.labels(route).inc()
if __name__ == '__main__':
start_http_server(9100)
while True:
handle('/projects')
5. Alerting Philosophy
Alerts should be: Actionable, Urgent, Owned. Avoid noisy alerts = alert fatigue.
| Anti-Pattern | Fix |
|---|---|
| Alert on every 500 | Alert on error rate > X% for Y mins |
| CPU > 80% single sample | Use sustained window + correlate with latency |
| Paging on low-priority batch failure | Triage via daily report |
Define SLO (Service Level Objective): e.g., 99.5% of requests under 300ms over 30 days. Alert when error budget burn rate spikes (fast consumption of the allowed failure budget).
6. Tracing Deep Dive
Trace = tree of spans. Each span: operation name, start time, duration, attributes, status.
Common span attributes: http.method, http.route, db.statement (sanitized), error flag. Propagate traceparent header (W3C standard) across service boundaries.
Benefits: * Pinpoint slow service or DB call. * Visualize fan-out patterns causing latency. * Combine with logs via trace_id for rich debugging.
7. Combining Pillars (Example Flow)
- User action triggers request (trace started).
- Middleware starts span, adds
trace_idto logging MDC (mapped diagnostic context). - Each downstream call creates child span (DB, cache, external API).
- Logs automatically include
trace_id. - Metrics histogram records latency buckets.
- On error, span status = error + log entry at
errorlevel triggers alert if rate threshold exceeded.
8. Dashboards & Visual Overlays
Essential starter panels: * Request rate (per route) + error rate overlay. * P95 and P99 latency with deploy markers. * Saturation: CPU, memory, queue depth. * Top error codes & top slow queries.
Overlay deployments (Git commit hash) so regressions correlate with release events.
9. Maturity Ladder
| Level | Characteristics | Next Step |
|---|---|---|
| 0: Blind | Logs only on local dev | Add centralized logging |
| 1: Basic | Centralized logs + basic metrics | Add tracing & structured logs |
| 2: Correlated | Trace IDs unify logs/metrics/traces | Define SLOs & alerting burn rates |
| 3: Proactive | SLO-based alerts, chaos drills | Add anomaly detection, auto-rollbacks |
Aim for Level 2 early in a project: biggest insight ROI.
10. Costs & Sampling
High cardinality (e.g., user_id labels) explodes storage cost. Strategies:
* Sample traces (keep all errors + small % of healthy).
Aggregate metrics labels sparingly (avoid free-form labels).
Log at info only essential business events; push verbose details to debug and sample them.
11. Local Development Tips
- Run a lightweight stack:
docker-composewith Prometheus + Grafana + Tempo (or Jaeger) + Loki. - Use OpenTelemetry SDK auto-instrumentation to bootstrap quickly.
- Practice reading a trace for a known bug to build team muscle memory.
12. Checklist
- Structured JSON logs with trace_id
- Central log aggregation configured
- Key service & route latency histograms
- Error rate + latency SLO defined
- Alert rules based on burn/error thresholds
- Distributed tracing enabled & propagated headers
- Dashboard: rate, errors, latency, saturation
- Sampling rules (errors full; success partial)
- Deployment markers on charts
- Run book for top 3 alerts
13. Quick Reference
| Need | Look At |
|---|---|
| Which service is slow? | Trace waterfall |
| Sudden 500 spike | Error rate panel + recent deploys |
| Intermittent latency | P95/P99 vs CPU/memory overlay |
| User reported issue | Search logs by trace_id or user id (hashed) |
| Capacity planning | Saturation panels + growth trend |
14. Resources
- https://opentelemetry.io/
- Prometheus + Grafana docs
- "Distributed Systems Observability" (Cindy Sridharan)
- Loki / Tempo (Grafana OSS stack)
- Honeycomb (high-cardinality observability SaaS)