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/
4.1 KiB
logz
Structured logger backed by log/slog with request-context enrichment and duck-typed error integration.
Purpose
logz provides a stable Logger interface and a New() constructor. It wraps
log/slog (stdlib) so no external logging library is required. It owns the request
correlation ID context key and extra-field context key, providing helpers to attach
and retrieve them. When an error passed to Logger.Error implements ErrorCode()
or ErrorContext(), those fields are automatically appended to the log record —
without importing xerrors.
Tier & Dependencies
Tier: 1
Imports: context, errors, log/slog, os (stdlib only)
Must NOT import: xerrors, rbac, launcher, or any other micro-lib module.
The xerrors bridge is achieved via private duck-typed interfaces (errorWithCode,
errorWithContext) — no import is needed.
Key Design Decisions
- Wraps
log/slogexclusively; no external logging library dependency. Seedocs/adr/ADR-001-slog-stdlib-backend.md. logzownsctxRequestIDKey{}andctxExtraFieldsKey{}. Any package that needs to attach a request ID to logs imports onlylogz. Seedocs/adr/ADR-002-requestid-context-ownership.md.Loggeris an exported interface;Newreturns it, not the concrete*slogLogger.WithreturnsLogger, not*slogLogger. Seedocs/adr/ADR-003-exported-logger-interface.md.Error(msg string, err error, args ...any)treatserras a first-class parameter, enabling automatic enrichment from duck-typed error interfaces.
Patterns
Creating a logger:
logger := logz.New(logz.Options{
Level: slog.LevelDebug,
JSON: true,
StaticArgs: []any{"service", "api", "env", "production"},
})
Basic logging:
logger.Info("server started", "port", 8080)
logger.Warn("retrying", "attempt", 3)
logger.Error("request failed", err, "path", "/users")
// If err is *xerrors.Err, error_code and context fields are added automatically
Child loggers:
reqLogger := logger.With("trace_id", traceID)
reqLogger.Info("handling request")
Request context enrichment:
// In middleware:
ctx = logz.WithRequestID(ctx, requestID)
ctx = logz.WithField(ctx, "user_id", userID)
ctx = logz.WithFields(ctx, map[string]any{"tenant": tenantID, "region": "eu"})
// In handler (picks up request_id and extra fields automatically):
reqLogger := logger.WithContext(ctx)
reqLogger.Info("processing order")
Retrieving the request ID (e.g. for response headers):
id := logz.GetRequestID(ctx)
Local Logger interface in a library (ADR-001 pattern):
// In your library — does NOT import logz
type Logger interface {
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, err error, args ...any)
}
// logz.Logger satisfies this interface; pass logz.New(...) from the app layer
What to Avoid
- Do not import
xerrorsfromlogz. The duck-type bridge (errorWithCode,errorWithContext) keeps the two packages decoupled. - Do not return
*slogLoggerfrom any exported function. The concrete type must stay unexported so the interface contract is the only public surface. - Do not write log output to
os.Stderror arbitraryio.Writers. Output always goes toos.Stdout; routing is the responsibility of the process supervisor. - Do not use
slog.Attrorslog.Groupin theLoggerinterface. Keep the variadickey, valueconvention for simplicity. - Do not call
WithContext(nil)— the method handlesnilsafely (returns the same logger), butWithRequestIDandWithFielddo not accept nil contexts. - Do not add new methods to the
Loggerinterface without a version bump. Any addition is a breaking change for all callers that replicate the interface locally.
Testing Notes
compliance_test.goasserts at compile time thatNew(Options{})satisfieslogz.Logger, enforcing the full method set at every build.logz_test.gocovers level filtering, JSON vs text mode,Withchaining,WithContextenrichment, and automatic error enrichment via duck-typed interfaces.- No external test dependencies — run with plain
go test.