Configuring slog
slogconf configures slog’s default logger from environment variables. Importing it is enough: its init reads the environment and installs the logger before your main runs.
import _ "github.com/psyb0t/slogging/slogconf"
Environment variables
| Variable | Values | Default |
|---|---|---|
LOG_LEVEL | debug, info, warn, error, in any case | info |
LOG_FORMAT | json, text, in any case | text |
LOG_ADD_SOURCE | anything Go’s strconv.ParseBool accepts: 1, t, true, TRUE, 0, f, false and so on | false |
A variable that is set but empty counts as unset, so LOG_LEVEL= left in a shell profile falls back to the default instead of failing. The names are exported as slogconf.EnvVarNameLevel, EnvVarNameFormat and EnvVarNameAddSource.
An invalid value is a startup failure. The blank import panics with the error, and Init returns it. The level is checked first, so with a bad level and a bad format at once you see the level error. The three sentinels are ErrInvalidLogLevel, ErrInvalidLogFormat and ErrInvalidLogAddSource, and errors.Is matches them through the wrapping. The exact messages are in Troubleshooting.
With the level at debug, configuration ends with one line saying what it picked:
{"time":"2026-11-10T07:09:49.381960536Z","level":"DEBUG","msg":"slogconf: configured","level":"debug","format":"json","addSource":false}
Output streams
The installed output sends debug and info records to stdout and warn and error records to stderr. To change the writers or the split point, build your own handlers.Handler and install it with SetOutput.
Your own variable names
LOG_LEVEL and friends are the defaults. Call Init with Options to read other variables:
import "github.com/psyb0t/slogging/slogconf"
if err := slogconf.Init(slogconf.Options{
LevelEnvVar: "MYAPP_LOG_LEVEL",
FormatEnvVar: "MYAPP_LOG_FORMAT",
}); err != nil {
panic(err)
}
Only the variables you name get read, so a stray LOG_LEVEL cannot override MYAPP_LOG_LEVEL. A field you leave empty keeps its default name.
| Field | Meaning | Empty means |
|---|---|---|
LevelEnvVar | Variable read for the level | LOG_LEVEL |
FormatEnvVar | Variable read for the format | LOG_FORMAT |
AddSourceEnvVar | Variable read for source reporting | LOG_ADD_SOURCE |
DefaultLevel | Level used when the variable is unset or empty. Validated like the variable. | info |
DefaultFormat | Format used when the variable is unset or empty. Validated like the variable. | text |
DefaultAddSource | Source reporting when the variable is unset or empty | false |
slogconf.Init(slogconf.Options{}) does exactly what the blank import does. The blank import still runs first whenever the package is imported, so your environment has to hold valid values for the default names too, or the process panics before Init is reached.
Call Init early. It replaces slog’s default logger and the whole handler chain, so it also discards any sink added before it. A logger derived with slog.Default().With(...) before Init keeps writing through the old chain.
Adding a destination or changing the output
These are different jobs with different calls:
slogconf.AddSink(ring) // ALSO send records here: appends
slogconf.SetOutput(handler) // send my output THERE instead: replaces, keeps sinks
slogconf.SetHandlers(h) // start over: replaces the output AND every sink
slogconf installs a handlers.FanOutHandler whose first slot is the output handler. AddSink appends to it. SetOutput replaces the first slot and leaves the sinks where they were.
The difference matters. The output already writes to stdout and stderr. Add a second terminal handler with AddSink and both receive every record, so every line prints twice. SetOutput is the call that swaps it.
SetHandlers(hs...) replaces everything with a new fan-out over hs. It is the escape hatch, mostly for tests that point everything at a buffer or io.Discard.
AddSink and SetOutput return false when slog’s default is no longer a fan-out this package installed, for example because other code called slog.SetDefault. The call still takes effect. AddSink wraps the foreign handler and the sink in a new fan-out. SetOutput replaces the whole chain with your handler. Either way the stdout and stderr split from slogconf is gone, which is worth checking for rather than discovering through missing logs:
if !slogconf.AddSink(ring) {
slog.Warn("default logger was replaced elsewhere; the ring is stacked on it")
}
Like Init, all three replace slog’s default logger, and loggers derived earlier with With or WithGroup keep the chain they captured.
Changing the level at runtime
The output that slogconf builds uses a fixed level. For a level you can change while the process runs, build a handlers.Handler with a *slog.LevelVar and install it with SetOutput.