# 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/slog` exclusively; no external logging library dependency. See `docs/adr/ADR-001-slog-stdlib-backend.md`. - `logz` owns `ctxRequestIDKey{}` and `ctxExtraFieldsKey{}`. Any package that needs to attach a request ID to logs imports only `logz`. See `docs/adr/ADR-002-requestid-context-ownership.md`. - `Logger` is an exported interface; `New` returns it, not the concrete `*slogLogger`. `With` returns `Logger`, not `*slogLogger`. See `docs/adr/ADR-003-exported-logger-interface.md`. - `Error(msg string, err error, args ...any)` treats `err` as a first-class parameter, enabling automatic enrichment from duck-typed error interfaces. ## Patterns **Creating a logger:** ```go logger := logz.New(logz.Options{ Level: slog.LevelDebug, JSON: true, StaticArgs: []any{"service", "api", "env", "production"}, }) ``` **Basic logging:** ```go 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:** ```go reqLogger := logger.With("trace_id", traceID) reqLogger.Info("handling request") ``` **Request context enrichment:** ```go // 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):** ```go id := logz.GetRequestID(ctx) ``` **Local Logger interface in a library (ADR-001 pattern):** ```go // 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 `xerrors` from `logz`. The duck-type bridge (`errorWithCode`, `errorWithContext`) keeps the two packages decoupled. - Do not return `*slogLogger` from 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.Stderr` or arbitrary `io.Writer`s. Output always goes to `os.Stdout`; routing is the responsibility of the process supervisor. - Do not use `slog.Attr` or `slog.Group` in the `Logger` interface. Keep the variadic `key, value` convention for simplicity. - Do not call `WithContext(nil)` — the method handles `nil` safely (returns the same logger), but `WithRequestID` and `WithField` do not accept nil contexts. - Do not add new methods to the `Logger` interface without a version bump. Any addition is a breaking change for all callers that replicate the interface locally. ## Testing Notes - `compliance_test.go` asserts at compile time that `New(Options{})` satisfies `logz.Logger`, enforcing the full method set at every build. - `logz_test.go` covers level filtering, JSON vs text mode, `With` chaining, `WithContext` enrichment, and automatic error enrichment via duck-typed interfaces. - No external test dependencies — run with plain `go test`.