feat(observability): add dual local logging sinks and static environment context
Why:
- Wanted human-readable console output while developing locally, without
losing a machine-parseable log for later grepping/parsing. A single
renderer chosen by a flag can't do both at once.
- ADR-0011 had no way to correlate an issue with a specific deployment
(build/region/instance) independent of any one request.
Changes:
- configure_logging() now builds two independent handlers: console (always
on, colored unless LOG_JSON_FORMAT=true) and an optional rotating JSON file
(LOG_FILE_PATH, unset by default) -- the same structlog event fans out to
both, so call sites are unaffected.
- A static structlog processor binds env/service_version onto every event.
Deliberately not a contextvar: RequestIdMiddleware's clear_contextvars()
would wipe a value bound there before the first request.
- New settings: APP_SERVICE_VERSION, LOG_FILE_PATH/LOG_FILE_MAX_BYTES/
LOG_FILE_BACKUP_COUNT.
- ADR-0011 amended with both decisions ("console and file are independent
sinks locally"; "bind process-level environment context once at startup").
Impact:
- configure_logging() signature changed to (logging_settings, app_settings);
both call sites (lifespan, qdrant_bootstrap CLI) updated.
This commit is contained in:
19
CLAUDE.md
19
CLAUDE.md
@@ -254,7 +254,24 @@ Postgres remains system of record for tenants, API keys, audit, jobs,
|
||||
`graph_runs`, `llm_calls`/`llm_pricing`. Correlate the two via `request_id`,
|
||||
`tenant_id`, `thread_id`, `run_id`. Use `structlog` with stable event names
|
||||
and structured fields (`logger.info("graph.run.completed", ...)`), not
|
||||
interpolated prose; JSON logs by default in production.
|
||||
interpolated prose; JSON logs by default in production, plus an optional
|
||||
local-only JSON file sink independent of the console renderer (`LOG_FILE_PATH`).
|
||||
|
||||
**Add logging in the same change that adds the code, not as a follow-up.**
|
||||
When you add a new service-level entry point (an `application/` function a
|
||||
route calls directly, an ingestion phase, a mutation) or a new failure branch
|
||||
inside one, add its `logger.*` event in that same diff, using ADR-0011's
|
||||
level/event-naming table. Deferring it means re-deriving the failure modes and
|
||||
field names later from code that no longer has them in working memory — as
|
||||
happened with `src/application/files/upload.py`, where four failure branches
|
||||
(`parse_failed`, `chunk_limit_exceeded`, `embedding_failed`, `index_failed`)
|
||||
shipped with no log event and had to be retrofitted.
|
||||
|
||||
This does not mean logging every function. Pure functions, models, schemas,
|
||||
and repositories (`infrastructure/postgres/repositories/`) stay silent by
|
||||
convention — the caller that turns their result into a business-meaningful
|
||||
outcome (job succeeded, upload rejected, domain disabled) is where the event
|
||||
belongs, not the row-level function underneath it.
|
||||
|
||||
## Testing (ADR-0016)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user