KeiSeiKit-1.0/_blocks/obs-structured-logs.md
Parfii-bot 48cff91056 feat(blocks): 3 observability blocks — logs/metrics/traces
- 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>
2026-04-21 20:41:17 +08:00

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.
  • msg stays 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.stack as a single string with \n separators (don't split across lines).

Language bindings (pick ONE per service, never two):

  • Rust: tracing + tracing-subscriber with .json() formatter [VERIFIED: docs.rs/tracing-subscriber]
  • Go: log/slog stdlib with slog.NewJSONHandler (Go 1.21+) [VERIFIED: pkg.go.dev/log/slog]
  • Python: structlog with JSONRenderer [VERIFIED: www.structlog.org]
  • Node/TS: pino (pino({ level, formatters })) [VERIFIED: getpino.io]
  • Swift/iOS: server-side only — swift-log with swift-log-formatter-json backend

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.