A thin, opinionated wrapper around Go's log/slog that builds a fully configured *slog.Logger from a small set of functional options and, optionally, LOG_* environment variables.
- JSON or text encoding — toggle with one option or one env var.
- Stdout or stderr destination — typed enum, not raw strings at the call-site.
- Runtime-mutable level — either supply your own
*slog.LevelVaror letNewWithLevelVarhand one back to you. - Pluggable handlers — drop in a caller-supplied
slog.Handler(Loki, Datadog, OpenTelemetry, …) and the option pipeline short-circuits out of your way. - Env-driven configuration — standard 12-factor pattern, prefix is configurable.
- Sentinel errors designed for
errors.Ismatching. - Never fails — invalid env values are silently ignored; the configured default is kept.
go get github.com/Arhius/slogxpackage main
import "github.com/Arhius/slogx"
func main() {
lg := slogx.New() // JSON, info level, stdout, installed as slog.Default
lg.Info("hello", "k", "v")
}Resolution order: defaults → options → environment. Later inputs overwrite earlier ones.
| Option | Default | Description |
|---|---|---|
WithWriter(io.Writer) |
os.Stdout |
Destination for log output. |
WithFormat(Format) |
JSONFormat |
JSONFormat or TextFormat. |
WithLevel(Level) |
InfoLevel |
DebugLevel, InfoLevel, WarnLevel, or ErrorLevel. |
WithAddSource(bool) |
false |
Adds source file:line on every record. |
WithSetDefault(bool) |
true |
Installs the logger via slog.SetDefault. |
WithLoadFromEnv(bool) |
true |
Toggles LOG_* env overrides. |
WithEnvPrefix(string) |
"LOG" |
Prefix used for env lookups. |
WithHandler(slog.Handler) |
— | Installs a custom handler. Short-circuits format/writer/level/add-source and their env overrides. |
WithLevelVar(*slog.LevelVar) |
— | Installs a caller-owned *slog.LevelVar. Wins over WithLevel regardless of option order. |
All names are built as <PREFIX>_<SUFFIX>. Prefix defaults to LOG.
| Variable | Accepted values | Maps to |
|---|---|---|
LOG_FORMAT |
json, text |
WithFormat |
LOG_LEVEL |
debug, info, warn, error |
WithLevel |
LOG_ADD_SOURCE |
anything strconv.ParseBool accepts |
WithAddSource |
LOG_OUTPUT |
stdout, stderr |
underlying writer |
Any variable whose value fails to parse is silently ignored; other variables still apply.
Two equivalent recipes — pick whichever fits your call-site better.
// (1) You already have a *slog.LevelVar (for instance because an admin endpoint needs to flip it):
lv := &slog.LevelVar{}
lv.Set(slog.LevelInfo)
lg := slogx.New(slogx.WithLevelVar(lv))
lv.Set(slog.LevelDebug) // takes effect on the next record// (2) You don't want to pre-allocate:
lg, lv := slogx.NewWithLevelVar(slogx.WithLevel(slogx.InfoLevel))
lv.Set(slog.LevelDebug)NewWithLevelVar always returns a non-nil *slog.LevelVar; if you also passed WithLevelVar(yours), that same pointer is returned unmodified.
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
lg := slogx.New(slogx.WithHandler(h))When WithHandler is set, the custom handler owns its own configuration. LOG_FORMAT, LOG_LEVEL, LOG_ADD_SOURCE, and LOG_OUTPUT are all no-ops. WithSetDefault and WithLoadFromEnv still apply.
Sentinel errors, matched with errors.Is:
ErrNilLogger— for downstream code reporting an unexpectedly nil*slog.Logger.ErrInvalidFormat,ErrInvalidLevel,ErrInvalidOutput— returned byParseFormat/ParseLevel/ParseOutput.
if _, err := slogx.ParseLevel(input); errors.Is(err, slogx.ErrInvalidLevel) {
// ...
}go test -race -cover ./... # tests + coverage (target: 100.0%)
go test -run=NONE -bench=. ./... # benchmarks
go vet ./...
gofmt -l .