KeiSeiKit-1.0/_blocks/obs-structured-logs.md
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +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.