Documentation
¶
Overview ¶
Package logger defines a minimal Logger port (hexagonal-style) and a few thin adapters around it. The goal is to:
- Decouple call sites from the concrete *slog.Logger so tests can swap in a no-op (or a capturing) implementation per test without touching package-level globals — directly enabling agent-side test isolation (one test run, one logger instance, no lingering handler state) and unblocking parallel test execution for any suite that previously serialized on slog.SetDefault.
- Leave the door open to future structured-logging back-ends (zap, zerolog, OpenTelemetry log SDK) without rewriting every call site — only the adapter changes.
Method signatures intentionally mirror slog.Logger's variadic `(msg string, args ...any)` shape so the migration is a search-and- replace at consumer sites: drop the `*slog.Logger` field type for `logger.Logger`, leave the call sites alone. Error is the one exception — it lifts `err error` into the signature because the existing call-site convention is `logger.Error(msg, "error", err)`, which is repetitive and easy to forget; the explicit error parameter makes the contract obvious and lets adapters attach the error with a canonical key.
This package has zero non-stdlib dependencies, so importing it from any kc/* sub-package is acyclic by construction.
Index ¶
- func AsSlog(l Logger) *slog.Logger
- type CaptureLogger
- func (c *CaptureLogger) Debug(_ context.Context, msg string, args ...any)
- func (c *CaptureLogger) Error(_ context.Context, msg string, err error, args ...any)
- func (c *CaptureLogger) Info(_ context.Context, msg string, args ...any)
- func (c *CaptureLogger) Records() []CaptureRecord
- func (c *CaptureLogger) Warn(_ context.Context, msg string, args ...any)
- func (c *CaptureLogger) With(args ...any) Logger
- type CaptureRecord
- type Logger
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AsSlog ¶
AsSlog returns the *slog.Logger from a Logger when the underlying implementation is a slogAdapter, otherwise nil. Used by the few remaining call sites that have to bridge to a slog-typed parameter during incremental migration. Returning nil rather than panicking keeps mock-Logger tests from blowing up if they hit a not-yet- migrated branch.
Types ¶
type CaptureLogger ¶
type CaptureLogger struct {
// contains filtered or unexported fields
}
CaptureLogger is a Logger that records every call into an in-memory slice. Useful in tests that want to assert "we logged X" without piping through slog handlers / io.Discard / regex matches.
Records is protected by a sync.Mutex so the same logger can be used from a goroutine spawned inside the system-under-test. With returns a child that shares the SAME mutex + records slice — you can attach the parent CaptureLogger and read from it after the system writes through any number of child loggers. Each child carries its own "with" prefix that gets prepended to every record's Args.
func (*CaptureLogger) Debug ¶
func (c *CaptureLogger) Debug(_ context.Context, msg string, args ...any)
func (*CaptureLogger) Info ¶
func (c *CaptureLogger) Info(_ context.Context, msg string, args ...any)
func (*CaptureLogger) Records ¶
func (c *CaptureLogger) Records() []CaptureRecord
Records returns a snapshot copy of all observed records. Safe to call concurrently with logger writes; the returned slice is independent of the live buffer.
func (*CaptureLogger) Warn ¶
func (c *CaptureLogger) Warn(_ context.Context, msg string, args ...any)
func (*CaptureLogger) With ¶
func (c *CaptureLogger) With(args ...any) Logger
With produces a child CaptureLogger that prefixes every record's Args with the supplied pairs. Records are recorded into the same underlying slice — there's only one buffer per Capture root.
type CaptureRecord ¶
CaptureRecord is a single observed log call. CaptureLogger appends one of these per Debug/Info/Warn/Error invocation. Args is the raw variadic slice the caller passed (for Error, the err is implicitly appended after "error" — exactly mirroring slogAdapter.Error so a CaptureLogger test exercises the same code path).
type Logger ¶
type Logger interface {
// Debug logs at debug level. Typically gated by LOG_LEVEL=debug.
Debug(ctx context.Context, msg string, args ...any)
// Info logs at info level. The default operational level.
Info(ctx context.Context, msg string, args ...any)
// Warn logs at warn level. Used for recoverable anomalies.
Warn(ctx context.Context, msg string, args ...any)
// Error logs at error level. err is conventionally attached under
// the "error" key; pass nil if no error object exists (rare —
// prefer Warn in that case).
Error(ctx context.Context, msg string, err error, args ...any)
// With returns a new Logger that prepends args to every record.
// Mirrors slog.Logger.With — useful for request-scoped or
// component-scoped enrichment ("request_id", id; "component",
// "billing"; etc.).
With(args ...any) Logger
}
Logger is the minimal structured-logging contract used across the codebase. Implementations are required to be safe for concurrent use; the canonical adapter (slogAdapter) is, since *slog.Logger is.
`args ...any` follows the slog convention: alternating (key string, value any) pairs, or a slog.Attr value, or a slog.Group. Adapters MAY normalise these but MUST NOT silently drop them — a dropped key is a dropped audit signal.
func NewNoop ¶
func NewNoop() Logger
NewNoop returns a Logger that discards every record.
Cheaper than NewSlog(slog.New(slog.NewTextHandler(io.Discard, nil))) because it short-circuits before any allocation — the slog path still allocates the slog.Record, formats the time, walks the args slice, etc., even when the handler is io.Discard. For a hot test loop the difference is real.