# `logz` > Structured logging backed by `log/slog` with automatic error enrichment. **Module:** `code.nochebuena.dev/go/logz` **Tier:** 1 — stdlib only (`log/slog`, `context`, `errors`, `os`) **Go:** 1.25+ **Dependencies:** none --- ## Overview `logz` wraps [`log/slog`](https://pkg.go.dev/log/slog) behind a simple `Logger` interface. It adds two things on top of plain slog: 1. **Automatic error enrichment** — `Error` inspects the error for `ErrorCode()` and `ErrorContext()` methods and appends the code and context fields to the log record automatically. This pairs with `xerrors.Err` without importing `xerrors`. 2. **Context propagation helpers** — `WithRequestID`, `WithField`, `WithFields` store values in `context.Context`; `WithContext` creates a child logger pre-loaded with those values. ## Installation ```sh go get code.nochebuena.dev/go/logz ``` ## Quick start ```go import ( "log/slog" "code.nochebuena.dev/go/logz" ) logger := logz.New(logz.Options{ Level: slog.LevelInfo, JSON: true, StaticArgs: []any{"service", "api"}, }) logger.Info("server started", "port", 8080) logger.Error("request failed", err) ``` ## Usage ### Creating a logger ```go // Zero value: INFO level, text output, no static args. logger := logz.New(logz.Options{}) // Production: JSON, custom level, static service tag. logger := logz.New(logz.Options{ Level: slog.LevelInfo, JSON: true, StaticArgs: []any{"service", "payments", "env", "prod"}, }) ``` The library does **not** read environment variables. Reading `LOG_LEVEL` or `LOG_JSON_OUTPUT` is the application's responsibility — pass the parsed values into `Options`. ### Logging ```go logger.Debug("cache miss", "key", cacheKey) logger.Info("user created", "user_id", id) logger.Warn("slow query", "duration_ms", 520) logger.Error("save failed", err, "table", "orders") ``` `Error` automatically enriches the log record when `err` satisfies the duck-type interfaces: | Method | What it adds | |--------|-------------| | `ErrorCode() string` | `error_code` attribute | | `ErrorContext() map[string]any` | all key-value pairs in the map | ### Child loggers ```go // Attach fixed attrs to every record from this logger. reqLogger := logger.With("request_id", id, "user_id", uid) // Attach attrs stored in context. reqLogger := logger.WithContext(ctx) ``` ### Context helpers ```go // Store values. ctx = logz.WithRequestID(ctx, requestID) ctx = logz.WithField(ctx, "user_id", userID) ctx = logz.WithFields(ctx, map[string]any{"tenant": "acme", "region": "us-east"}) // Retrieve. id := logz.GetRequestID(ctx) // Build a child logger with all context values pre-attached. reqLogger := logger.WithContext(ctx) ``` `WithFields` merges with any existing fields in the context — it does not overwrite them. ## Design decisions **No singleton** — `logz.New(opts)` returns a plain value. Each component that needs logging receives a `logz.Logger` via constructor injection. Tests can create isolated loggers without global state. **`Error` replaces `LogError`** — enrichment is automatic and zero-overhead when the error is a plain `error`. Callers need only one method instead of two. **`Fatal` removed** — calling `os.Exit(1)` inside a library is untestable and bypasses deferred cleanup. Callers log the error then decide how to exit: ```go logger.Error("fatal startup failure", err) os.Exit(1) ``` **No env-var reading** — libraries should not read environment variables. The application reads `LOG_LEVEL`/`LOG_JSON_OUTPUT` and passes parsed values into `Options`. **Duck-typing bridge** — `logz` defines private `errorWithCode` and `errorWithContext` interfaces. `xerrors.Err` satisfies both structurally — no import of `xerrors` is needed. ## Ecosystem ``` Tier 0: xerrors ↑ (duck-types — no direct import) Tier 1: logz ← you are here ↑ Tier 2: httpclient, httputil ↑ Tier 4: httpmw, httpauth, httpserver ``` ## License MIT