feat(logz): initial stable release v0.9.0

Structured logger backed by log/slog with request-context enrichment, extra-field context helpers, and duck-typed automatic error enrichment.

What's included:
- `Logger` interface with Debug / Info / Warn / Error / With / WithContext; `New(Options)` constructor writing to os.Stdout
- `WithRequestID` / `GetRequestID` and `WithField` / `WithFields` context helpers — package owns both context keys
- Automatic error_code and context-field enrichment in Logger.Error via duck-typed errorWithCode / errorWithContext interfaces (no xerrors import)

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
This commit is contained in:
2026-03-18 13:31:39 -06:00
commit 3667b92fab
15 changed files with 1020 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
# ADR-002: RequestID Context Ownership
**Status:** Accepted
**Date:** 2026-03-18
## Context
Global ADR-003 establishes that context helpers must live with their data owners.
The request correlation ID (`request_id`) is a logging concern — it is used
exclusively to enrich log records. Therefore its context key and helpers belong in
`logz`, not in an HTTP module or a generic `ctx` package.
If the key were defined in an HTTP middleware package, any non-HTTP component that
wanted to attach a correlation ID to logs would need to import an HTTP package. If
the key were defined in a separate context utility package, that package would
become an implicit dependency of both the HTTP layer and the logging layer with no
clear owner.
## Decision
Two unexported context key types are defined in `context.go`:
- `ctxRequestIDKey struct{}` — key for the correlation ID string.
- `ctxExtraFieldsKey struct{}` — key for a `map[string]any` of arbitrary extra log fields.
Four exported helpers manage these keys:
- `WithRequestID(ctx, id) context.Context` — stores the request ID.
- `GetRequestID(ctx) string` — retrieves it, returning `""` when absent or when `ctx` is nil.
- `WithField(ctx, key, value) context.Context` — adds one key-value pair to the extra fields map.
- `WithFields(ctx, fields) context.Context` — merges multiple pairs; does not overwrite
unrelated existing fields.
`Logger.WithContext(ctx) Logger` reads both keys and returns a child logger with the
found values pre-attached as attributes. The method returns the same logger unchanged
when neither key is present, avoiding an unnecessary allocation.
## Consequences
- Any package — HTTP middleware, gRPC interceptor, background worker — can attach a
request ID to the context by importing only `logz`. No HTTP package is needed.
- `WithFields` merges into a new map rather than mutating the existing one, so
middleware stages can add fields without affecting the context seen by upstream handlers.
- The unexported key types prevent key collisions: no external package can construct
or compare `ctxRequestIDKey{}` directly.
- `GetRequestID` is provided for diagnostic use (e.g. adding the request ID to an
error response header) without requiring the caller to call through the `Logger` API.