- obs-structured-logs.md: JSON-lines + W3C trace_id correlation - obs-metrics.md: Prom + OTel + RED/USE + cardinality budget - obs-traces.md: OTel + W3C traceparent + sampling + OTLP Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2.7 KiB
2.7 KiB
OBSERVABILITY — Structured logs (JSON-lines)
Structured logging is the cheapest leg of the observability triad. One JSON object per line, stable field names, machine-parseable by any log shipper (Loki, Vector, Fluent Bit, Datadog Agent, CloudWatch). Unstructured printf / logger.info("user %s did %s", u, a) wastes the capability.
Field taxonomy (stable across services — single source of truth):
| Field | Type | Meaning |
|---|---|---|
ts |
RFC3339 string | Timestamp with timezone (2026-04-21T12:00:00.123Z) |
level |
enum | debug / info / warn / error / fatal |
msg |
string | Short human-readable summary (no interpolated values — they go in their own fields) |
service |
string | Emitting service name (e.g. api-gateway) |
env |
enum | local / dev / staging / prod |
trace_id |
hex32 | W3C traceparent trace-id (links log to trace — see obs-traces) |
span_id |
hex16 | W3C span-id of the current span |
request_id |
string | Per-request correlation ID (propagate via X-Request-ID) |
user_id |
string | Actor (redact PII — hash or internal ID, never email) |
err |
object | {type, message, stack} when level >= error |
Emission rules:
- Always write to stdout (one JSON per line). Let the container runtime / systemd capture it. Never open a log file from the app — shippers have file-locking races.
- NEVER mix plain text and JSON on stdout (breaks parsers). Config libraries must emit JSON in all environments, local included.
msgstays constant per log site (e.g."db query failed"). Dynamic values (query, duration_ms, table) go in their own fields. This is what makes logs queryable.- On exception: capture
err.stackas a single string with\nseparators (don't split across lines).
Language bindings (pick ONE per service, never two):
- Rust:
tracing+tracing-subscriberwith.json()formatter [VERIFIED: docs.rs/tracing-subscriber] - Go:
log/slogstdlib withslog.NewJSONHandler(Go 1.21+) [VERIFIED: pkg.go.dev/log/slog] - Python:
structlogwithJSONRenderer[VERIFIED: www.structlog.org] - Node/TS:
pino(pino({ level, formatters })) [VERIFIED: getpino.io] - Swift/iOS: server-side only —
swift-logwithswift-log-formatter-jsonbackend
Shipping:
- Container / k8s: stdout → Fluent Bit / Vector → Loki or vendor.
- Bare metal: systemd journald →
journalctl -o json→ Vector. - Dev: stdout is enough; no shipper.
Forbidden: string interpolation in msg (f"user {id}" — id goes in its own field); writing secrets to logs (token/password/cookie values); print() debug leftovers in committed code; changing level semantics per service (keep the 5 levels stable kit-wide); logging full request/response bodies without redaction.