Files
logz/CLAUDE.md
Rene Nochebuena 3667b92fab 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/
2026-03-18 13:31:39 -06:00

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/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:

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 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.Writers. 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.