feat(httpmw): initial stable release v0.9.0
Standalone net/http middleware for panic recovery, CORS, request ID injection, and request logging. What's included: - Recover(): panic -> 500, captures debug.Stack, no logger required - CORS(origins): OPTIONS 204 preflight, origin allowlist, package-wide method/header constants - RequestID(generator): injects ID via logz.WithRequestID, sets X-Request-ID response header - RequestLogger(logger): logs method/path/status/latency/request_id; Error for 5xx, Info otherwise - Logger interface: Info, Error, With — duck-typed; satisfied by logz.Logger - StatusRecorder: exported http.ResponseWriter wrapper that captures written status code - Direct logz import for context helpers (documented exception to ADR-001) Tested-via: todo-api POC integration Reviewed-against: docs/adr/
This commit is contained in:
53
docs/adr/ADR-001-logz-context-import-exception.md
Normal file
53
docs/adr/ADR-001-logz-context-import-exception.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# ADR-001: Direct logz Import for Context Helpers (Exception to Global ADR-001)
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
Global ADR-001 states that modules receiving a logger from the application layer
|
||||
should duck-type the logger injection point with a private interface (e.g.
|
||||
`type Logger interface { Info(...); Error(...) }`), so the application is not forced
|
||||
to import `logz` just to satisfy a concrete type.
|
||||
|
||||
`httpmw` follows this rule for its `Logger` interface in `logger.go`. Any struct
|
||||
with the right method set satisfies it.
|
||||
|
||||
However, `httpmw` also calls `logz.WithRequestID` (in `requestid.go`) and
|
||||
`logz.GetRequestID` (in `logger.go`) to store and read the request ID from context.
|
||||
These functions use an **unexported context key type** defined inside `logz`:
|
||||
|
||||
```go
|
||||
// inside logz — not exported
|
||||
type contextKey string
|
||||
const requestIDKey contextKey = "request_id"
|
||||
```
|
||||
|
||||
A context value stored with that key can only be read back with the same key. If
|
||||
`httpmw` tried to replicate the storage using its own unexported key, the values set
|
||||
by `logz.WithRequestID` would be invisible to `logz.GetRequestID`, and vice-versa —
|
||||
breaking the downstream `logz.Logger.WithContext` integration.
|
||||
|
||||
## Decision
|
||||
|
||||
`httpmw` imports `logz` directly for context helper access (`logz.WithRequestID`,
|
||||
`logz.GetRequestID`). This is an **explicit, documented exception** to the
|
||||
duck-typed Logger rule in global ADR-001.
|
||||
|
||||
The Logger injection point remains duck-typed (`httpmw.Logger` interface in
|
||||
`logger.go`). The exception applies only to the two context functions, not to the
|
||||
logger parameter of `RequestLogger`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `httpmw` has a direct module dependency on `logz` in its `go.mod`. Upgrading
|
||||
`logz` is a breaking change for `httpmw` if the context helper signatures change.
|
||||
- `logz.WithRequestID` and `logz.GetRequestID` form a shared context contract
|
||||
between `httpmw` (which stores the ID) and `logz.Logger` (which reads it when
|
||||
enriching log records). Both sides of the contract must be the same package.
|
||||
- Applications that use `httpmw` without `logz` as their logger will still get
|
||||
request IDs injected into the context correctly — `RequestID` middleware works
|
||||
independently. The ID simply won't be picked up automatically by a non-logz logger.
|
||||
- This exception must not be used as a precedent for importing logz elsewhere without
|
||||
justification. The rule remains: duck-type logger injection; import logz only when
|
||||
the unexported context key contract requires it.
|
||||
Reference in New Issue
Block a user