• v1.0.0 05c99f72ff

    Rene Nochebuena released this 2026-05-11 18:51:47 -06:00 | 1 commits to main since this release

    v1.0.0

    code.nochebuena.dev/go/logz

    Overview

    logz v1.0.0 commits the structured logging API as stable. All v0.9.0 roadmap
    items are resolved. The module ships a minimal six-method Logger interface
    backed by stdlib log/slog, with zero external dependencies, owned context keys
    for request correlation, and a duck-typed xerrors enrichment bridge validated
    under concurrent production load.

    What Changed Since v0.9.0

    New: Options.Writer io.Writer

    type Options struct {
        Level      slog.Level
        JSON       bool
        StaticArgs []any
        Writer     io.Writer // nil → os.Stdout
    }
    

    The output destination is now configurable. When Writer is nil the behavior is
    identical to v0.9.0 (writes to os.Stdout). Any io.Writer implementation is
    accepted:

    // Test log capture
    var buf bytes.Buffer
    l := logz.New(logz.Options{Writer: &buf})
    
    // File-based audit trail
    f, _ := os.OpenFile("audit.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    l := logz.New(logz.Options{Writer: f, JSON: true})
    
    // Both stdout and file
    l := logz.New(logz.Options{Writer: io.MultiWriter(os.Stdout, f), JSON: true})
    

    Roadmap items resolved

    Item Resolution
    io.Writer configurable Options.Writer — nil defaults to os.Stdout
    Log sampling No — belongs in slog.Handler wrappers or the observability layer
    Concurrent xerrors enrichment Validated — ErrorContext() is read-only post-construction
    Logger interface is final Committed — 6 methods; Fatal/Panic intentionally excluded
    WithFields merge concurrency Validated — context.WithValue immutability guarantees safety

    Full API (stable)

    Logger — interface: Debug, Info, Warn, Error(msg, err, args...),
    With(args...) Logger, WithContext(ctx) Logger.

    OptionsLevel slog.Level, JSON bool, StaticArgs []any,
    Writer io.Writer (nil → os.Stdout). Zero value is valid.

    New(opts Options) Logger — constructor; returns Logger, never the concrete type.

    WithRequestID(ctx, id) context.Context — stores a correlation ID under this
    package's private context key.

    GetRequestID(ctx) string — retrieves the ID; returns "" if absent or ctx nil.

    WithField(ctx, key, value) context.Context — adds a single field to the context.

    WithFields(ctx, fields) context.Context — merges multiple fields; does not
    overwrite existing keys.

    Logger.WithContext(ctx) Logger — child logger pre-enriched with request_id
    and any extra fields from the context; returns the same logger if ctx carries no
    relevant values.

    Automatic error enrichmentLogger.Error inspects err for ErrorCode()
    and ErrorContext() duck-type methods (satisfied by *xerrors.Err) and appends
    error_code and context fields automatically. No import between logz and
    xerrors is required in either direction.

    Migration from v0.9.0

    No breaking changes. Options{} continues to write to os.Stdout.

    go get code.nochebuena.dev/go/logz@v1.0.0
    
    Downloads
  • v0.9.0 3667b92fab

    Rene Nochebuena released this 2026-03-18 13:33:16 -06:00 | 2 commits to main since this release

    v0.9.0

    code.nochebuena.dev/go/logz

    Overview

    logz provides a stable Logger interface backed by the stdlib log/slog package, with no external logging library dependencies. It owns the request correlation ID context key and an extra-fields context key, supplying helpers to attach and retrieve them so any downstream handler can enrich log records from the context without manual field threading. When an error passed to Logger.Error implements the ErrorCode() or ErrorContext() duck-type interfaces, the structured fields are automatically appended to the log record — without logz importing xerrors.

    What's Included

    • Logger interface — Debug, Info, Warn, Error(msg, err, args...), With, WithContext
    • Options struct — Level slog.Level, JSON bool, StaticArgs []any
    • New(opts Options) Logger — constructs a Logger writing to os.Stdout; zero value of Options is valid (INFO level, text output)
    • WithRequestID(ctx, id string) context.Context — stores a correlation ID in the context; owned by this package
    • GetRequestID(ctx context.Context) string — retrieves the correlation ID; returns "" if absent or ctx is nil
    • WithField(ctx, key string, value any) context.Context — adds a single key-value logging field to the context
    • WithFields(ctx context.Context, fields map[string]any) context.Context — merges multiple key-value fields into the context; does not overwrite existing fields
    • Automatic error_code and context-field enrichment in Logger.Error via duck-typed errorWithCode / errorWithContext private interfaces

    Installation

    require code.nochebuena.dev/go/logz v0.9.0
    

    Design Highlights

    • Wraps log/slog exclusively — no external logging library is required or vendored (see docs/adr/ADR-001-slog-stdlib-backend.md).
    • logz owns the context keys ctxRequestIDKey{} and ctxExtraFieldsKey{} as unexported struct types, preventing collisions with any other package's context keys (see docs/adr/ADR-002-requestid-context-ownership.md).
    • New returns the Logger interface, not the concrete *slogLogger, and With also returns Logger — the concrete type is never exported, making the interface the only public surface and allowing safe local re-declaration in libraries (see docs/adr/ADR-003-exported-logger-interface.md).
    • Error treats err as a first-class parameter (not a variadic arg), enabling the automatic duck-typed enrichment path; xerrors.Err satisfies this without any import between the two packages.

    Known Limitations & Edge Cases

    • All log output is always written to os.Stdout. There is no configurable io.Writer — routing to files, syslog, or other sinks must be handled by the process supervisor or a shell redirect.
    • There is no log sampling or rate limiting. A high-frequency error loop will produce one log record per call with no throttling.
    • WithField and WithFields create a new map and a new context value on every call. In hot paths (e.g. per-request middleware), repeated calls accumulate allocations; batch them with a single WithFields call where possible.
    • WithContext(nil) is handled safely (returns the same logger), but WithRequestID and WithField do not accept nil contexts and will panic.
    • StaticArgs in Options are attached at logger construction time only; there is no way to inject static args into an existing logger without creating a new one via With.
    • Logger.With returns a Logger interface — if the caller needs WithContext after With, that is supported, but the full method set of slogLogger is not accessible after With.

    v0.9.0 → v1.0.0 Roadmap

    • Evaluate whether a configurable io.Writer (passed via Options) is required for production use cases such as test log capture or file-based audit trails.
    • Decide whether log sampling belongs in this package or should be left to slog.Handler wrappers, and document the recommended pattern.
    • Validate the duck-typed xerrors enrichment path under concurrent production load to confirm no race conditions on the ErrorContext() map read path.
    • Confirm that the Logger interface method set is final before 1.0; any addition is a breaking change for all callers that re-declare the interface locally.
    • Achieve production validation of the WithFields merge semantics under concurrent middleware chains.

    v0.9.0 rationale: The API is stable and intentional — designed through multiple architecture reviews and tested end-to-end via the todo-api POC (SQLite, RBAC, middleware stack, HTTP handlers). The module is not yet battle-tested in production for all edge cases, and the pre-1.0 designation preserves the option for minor API refinements based on real-world use.

    Downloads