KeiSeiKit-1.0/_blocks/obs-structured-logs.md
Parfii-bot a4e667de10 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

38 lines
2.7 KiB
Markdown

# 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.