Files
Cyrene_v2/internal/logger/logger.go
T

92 lines
2.6 KiB
Go

// Package logger provides the project-wide structured logging facade built on
// log/slog. All modules must use this package instead of creating their own
// logger instances. Development builds render human-friendly TextHandler output;
// production builds render JSON for machine parsing.
package logger
import (
"io"
"log/slog"
"os"
"strings"
)
// Format selects the structured log output encoding.
type Format string
const (
// FormatText is the human-readable development format.
FormatText Format = "text"
// FormatJSON is the machine-parseable production format.
FormatJSON Format = "json"
)
// Options configures a Logger.
type Options struct {
// Level is the minimum level to record, one of debug, info, warn, error.
// Unknown values fall back to info.
Level string
// Format is the output encoding, one of text, json. Unknown values fall
// back to text.
Format string
// Out is the destination writer. Defaults to os.Stderr when nil.
Out io.Writer
}
// New builds a slog.Logger from opts.
func New(opts Options) *slog.Logger {
level := ParseLevel(opts.Level)
out := opts.Out
if out == nil {
out = os.Stderr
}
handlerOpts := &slog.HandlerOptions{Level: level}
var handler slog.Handler
if Format(opts.Format) == FormatJSON {
handler = slog.NewJSONHandler(out, handlerOpts)
} else {
handler = slog.NewTextHandler(out, handlerOpts)
}
return slog.New(handler)
}
// ParseLevel maps a string level name to a slog.Level. Unknown or empty values
// produce slog.LevelInfo.
func ParseLevel(s string) slog.Level {
switch strings.ToLower(strings.TrimSpace(s)) {
case "debug":
return slog.LevelDebug
case "info":
return slog.LevelInfo
case "warn", "warning":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
// WithSource returns a child logger that tags every emitted record with a
// source so issues can be traced back to the owning module/sub-feature. source
// is conventionally "module/subfeature", e.g. "runtime/loop" or
// "channel/adapter". Every module that logs should call this once on its base
// logger so all records carry provenance.
func WithSource(l *slog.Logger, source string) *slog.Logger {
if l == nil {
return nil
}
return l.With(slog.String("source", source))
}
// Scoped returns a child logger tagged with module and subfeature, for finer
// provenance when a module contains several subsystems.
func Scoped(l *slog.Logger, module, subfeature string) *slog.Logger {
if l == nil {
return nil
}
return l.With(slog.String("module", module), slog.String("sub", subfeature))
}