84 lines
2.7 KiB
Go
84 lines
2.7 KiB
Go
// Package logger wraps log/slog with a thin, ergonomic API.
|
|
//
|
|
// The key addition over raw slog is the WithField / WithFields helpers that
|
|
// return a *Logger (not a *slog.Logger), so callers stay in the typed world
|
|
// and can chain field attachments without importing slog directly.
|
|
//
|
|
// Usage:
|
|
//
|
|
// log := logger.New("info").Expect("create logger")
|
|
// log.Info("server started")
|
|
//
|
|
// req := log.WithField("request_id", rid).WithField("user_id", uid)
|
|
// req.Info("handling request")
|
|
package logger
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
|
|
"gitea.djmil.dev/go/template/pkg/result"
|
|
)
|
|
|
|
// Logger is a thin wrapper around *slog.Logger.
|
|
// All slog methods (Info, Error, Debug, Warn, …) are available via embedding.
|
|
type Logger struct {
|
|
*slog.Logger
|
|
}
|
|
|
|
// New creates a JSON logger writing to stderr for the given level string.
|
|
// Valid levels: debug, info, warn, error.
|
|
func New(level string) result.Expect[*Logger] {
|
|
lvl := parseLevel(level)
|
|
if lvl.Err() != nil {
|
|
return result.Errf[*Logger]("parseLevel: %w", lvl.Err())
|
|
}
|
|
|
|
handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: lvl.Value()})
|
|
logger := &Logger{slog.New(handler)}
|
|
|
|
return result.Ok(logger)
|
|
}
|
|
|
|
// NewDevelopment creates a human-friendly text logger writing to stderr.
|
|
// Use this in local dev; prefer New() in any deployed environment.
|
|
func NewDevelopment() *Logger {
|
|
h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
|
|
|
|
return &Logger{slog.New(h)}
|
|
}
|
|
|
|
// NewNop returns a no-op logger. Useful in tests that don't care about logs.
|
|
func NewNop() *Logger {
|
|
return &Logger{slog.New(slog.NewTextHandler(io.Discard, nil))}
|
|
}
|
|
|
|
// WithField returns a child logger that always includes key=value in every log line.
|
|
func (l *Logger) WithField(key string, value any) *Logger {
|
|
return &Logger{l.Logger.With(key, value)}
|
|
}
|
|
|
|
// WithFields returns a child logger enriched with every key/value in fields.
|
|
// Prefer WithField for one or two fields; use WithFields for structured context
|
|
// objects (e.g. attaching a request span).
|
|
func (l *Logger) WithFields(fields map[string]any) *Logger {
|
|
args := make([]any, 0, len(fields)*2)
|
|
for k, v := range fields {
|
|
args = append(args, k, v)
|
|
}
|
|
|
|
return &Logger{l.Logger.With(args...)}
|
|
}
|
|
|
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
func parseLevel(level string) result.Expect[slog.Level] {
|
|
var lvl slog.Level
|
|
if err := lvl.UnmarshalText([]byte(level)); err != nil {
|
|
return result.Errf[slog.Level]("unknown level %q (use debug|info|warn|error)", level)
|
|
}
|
|
|
|
return result.Ok(lvl)
|
|
}
|