A small, opinionated logging library built on the standard library's
structured logging package log/slog.
It adds what slog doesn't give you out of the box:
- Extra levels for services —
HTTPfor request logs andCRITICALfor pager-worthy failures, plus anONLYlevel for temporary debugging. - Pretty, colored terminal output for development, powered by
lmittmann/tint, with JSON and logfmt output for production. - Runtime-adjustable log level — goroutine-safe, no restart needed.
- Fan-out to multiple handlers (e.g. colored console + JSON file) via
samber/slog-multi. - Ready-made
net/httpmiddleware for structured request logging (works great with go-chi). - Full
sloginteroperability — levels are plainslog.Levelvalues, every logger wraps a*slog.Logger, and any third-partyslog.Handlercan be plugged in.
Requires Go 1.26+.
go get github.com/altafino/loggerpackage main
import "github.com/altafino/logger"
func main() {
// The default logger writes pretty, colored output to stderr at LevelInfo.
logger.Info("server started", "port", 8080, "tls", true)
logger.Warn("connection pool nearly full", "used", 95, "max", 100)
logger.Error("payment failed", logger.Err(err), "order", 1234)
}Attributes are alternating key/value pairs (or slog.Attr values), exactly
as in slog. logger.Err(err) renders the error in red under the pretty
format and as a regular err attribute everywhere else.
Call Init once at startup:
logger.Init(
logger.WithLevel(logger.LevelDebug),
logger.WithFormat(logger.FormatJSON), // FormatPretty (default), FormatJSON, FormatText
logger.WithSource(), // add file:line of the call site
logger.WithAttrs("service", "billing", "version", "1.4.2"),
)log := logger.New(
logger.WithWriter(logFile),
logger.WithFormat(logger.FormatJSON),
logger.WithLevel(logger.LevelHTTP),
)
log.Info("hello", "answer", 42)
reqLog := log.With("request_id", id) // attrs attached to every record
reqLog.Info("cart fetched", "items", 3)Levels are ordinary slog.Level values. From most to least verbose:
| Level | Value | Purpose |
|---|---|---|
LevelDebug |
-4 | Development diagnostics |
LevelHTTP |
-2 | HTTP request/access logs |
LevelInfo |
0 | Normal operational messages (default minimum) |
LevelWarn |
4 | Unexpected, but not an error |
LevelError |
8 | Operation failed |
LevelCritical |
12 | Failure needing immediate attention |
LevelOnly |
16 | Temporary "print this no matter what" debugging |
LevelDisabled |
max | Suppresses all output (as a minimum level) |
The rule is the same one slog uses, with no special cases: a record is
emitted when its level is ≥ the logger's minimum level. So at the default
LevelInfo, Debug and HTTP records are hidden and everything else is
shown. Set the minimum to LevelHTTP or LevelDebug to see request logs.
logger.Only(...) logs at the highest level, so it is always printed
(unless logging is disabled). Setting the minimum level to LevelOnly
silences everything except Only calls — handy when you want to focus on
one spot in a noisy codebase:
logger.SetLevel(logger.LevelOnly) // hush everything else
logger.Only("checkout state", "cart", cart)The minimum level is held in a slog.LevelVar: changing it is goroutine-safe,
takes effect immediately, and propagates to loggers derived with
With/WithGroup.
logger.SetLevel(logger.LevelDebug) // default logger
log.SetLevel(logger.LevelDisabled) // a specific instancelevel, err := logger.ParseLevel(os.Getenv("LOG_LEVEL")) // "debug", "http", "info", "warn",
if err != nil { // "error", "critical", "only", "disabled"
level = logger.LevelInfo
}
logger.SetLevel(level)ParseFormat does the same for "pretty", "json" and "text".
| Format | Handler | Use case |
|---|---|---|
FormatPretty |
tint (colored, human) |
Development terminals (default) |
FormatJSON |
slog.JSONHandler |
Production log pipelines |
FormatText |
slog.TextHandler |
logfmt-style key=value |
Color is auto-detected (TTY check, honors NO_COLOR) and can be forced with
WithColor(true/false). The timestamp layout is configurable with
WithTimeFormat(layout).
import (
"github.com/altafino/logger"
"github.com/altafino/logger/middleware"
"github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware"
)
r := chi.NewRouter()
r.Use(
chimw.RequestID,
middleware.Logger, // logs via the default logger, or:
// middleware.New(myLogger), // logs via a specific instance
chimw.Recoverer,
)Each request is logged at LevelHTTP with structured attributes: method,
status, path, remote, bytes, duration and (when chi's RequestID
middleware is installed) request_id. Remember that request logs only appear
when the minimum level is LevelHTTP or lower.
Send pretty output to the terminal and JSON to a file:
jsonFile := slog.NewJSONHandler(f, nil)
logger.Init(
logger.WithFormat(logger.FormatPretty),
logger.WithExtraHandlers(jsonFile),
)The logger's minimum level gates all handlers; each extra handler may filter further on its own.
Any slog.Handler works — the logger's dynamic level still applies on top:
log := logger.New(logger.WithHandler(myHandler))slog.SetDefault(logger.Default().Slog()) // route plain slog calls through this logger
log.Slog().LogAttrs(ctx, slog.LevelInfo, "msg", slog.Int("n", 1))The old API (InitLogger, Settings, InfoLevel, …) still compiles via
deprecated aliases, but the semantics were fixed:
- Level ordering is now strict. Previously
Debug(4) was numerically aboveCritical(3) andError/Criticalwere printed regardless of the configured level. Now levels filter purely by severity: atLevelCriticalyou see onlyCriticalandOnlyrecords. - Calls take a message first.
logger.Info(value1, value2)becomeslogger.Info("message", "key1", value1, "key2", value2). logger.Httpis deprecated in favor oflogger.HTTP.Settings/InitLogger/LoggerSettingsare deprecated in favor ofInit(...Option); writing toLoggerSettingsdirectly never worked reliably and now has no effect.
See LICENSE.