Production-grade structured logger for Go services. Built on the standard
library's log/slog so any consumer of slog can use it as a drop-in.
- Structured — JSON or text, key=value attrs, child loggers via
.With(...). - Rotating files — size / age / backup count / gzip via lumberjack.
- Multi-sink — stdout + file + custom writer; each can have its own level and encoder.
- Async writes — bounded queue, drop-or-block policy. Hot path is ~100 ns/op and zero allocs.
- Sampling —
nrecords per(level, message)per second; bounds log volume in hot loops. - Hooks — fire Slack/Sentry/custom webhooks on ERROR (or any level set). AsyncHook adapter so a slow webhook never slows logging.
- slog-compatible — returns
*slog.Logger, so the rest of your code uses the standard API and OpenTelemetry /log/slogmiddleware works.
go get github.com/lbtsm/go-loggerGo 1.21+ required (log/slog).
import logger "github.com/lbtsm/go-logger"
lg := logger.NewProduction("/var/log/app.log")
defer lg.Close()
lg.Info("server started", "port", 8080)
lg.Error("upstream failed", "addr", "10.0.0.1:5432", "err", err)lg := logger.New(
logger.WithLevel(slog.LevelInfo),
logger.WithStdout(logger.FormatJSON),
logger.WithFile("/var/log/app.log",
logger.WithMaxSizeMB(100),
logger.WithMaxBackups(7),
logger.WithMaxAgeDays(30),
logger.WithCompress(),
),
logger.WithAsync(1024), // queue size; drop policy via WithAsyncDropOnFull
logger.WithSampler(100), // 100 records / msg / sec
logger.WithFields(slog.String("svc", "monitor"), slog.String("env", "prod")),
logger.WithHook(myHook),
)
defer lg.Close()// Built-in HTTP webhook (Slack/Discord/custom).
hook := logger.NewWebhookHook("https://hooks.slack.com/services/XXX",
logger.WithWebhookLevels(slog.LevelError),
logger.WithWebhookTimeout(5*time.Second),
)
// Non-blocking dispatch.
async := logger.NewAsyncHook(hook, logger.WithAsyncHookQueueSize(256))
defer async.Close()
lg := logger.New(logger.WithHook(async))Implement the Hook interface for custom destinations:
type Hook interface {
Levels() []slog.Level
Fire(ctx context.Context, r slog.Record) error
}lg := logger.NewProduction("/var/log/app.log") // JSON to stdout+file, async, INFO
lg := logger.NewDevelopment() // text to stdout, DEBUG, source onlogger.SetDefault(logger.NewProduction("/var/log/app.log"))
logger.Info("hello", "k", "v") // uses defaultSetDefault is safe to call concurrently with Info / Warn / etc.
Benchmarks on Apple M3, Go 1.25:
| benchmark | ns/op | allocs/op |
|---|---|---|
BenchmarkAsyncWriter_Write |
~101 | 0 |
BenchmarkSampling_HotPath |
~20 | 0 |
Combined with slog's own zero-alloc JSON encoder, a full lg.Info("msg", "k", v)
typically runs in a few hundred nanoseconds on warm CPU paths.
┌──────────────┐
slog.Logger ─▶│ Handler │──────▶ sink 1 (slog.JSONHandler) ─▶ AsyncWriter ─▶ lumberjack ─▶ file
│ (fan-out) │──────▶ sink 2 (slog.TextHandler) ───────────────▶ stdout
│ │──────▶ hook 1 (WebhookHook)
└──────────────┘──────▶ hook 2 (AsyncHook → custom)
Handleris a slog.Handler that fans out to N sinks and hooks.- Each sink is itself a slog.Handler (JSON or Text), optionally wrapped in
SamplingHandler. - File sinks may be wrapped in
AsyncWriter(background goroutine, bounded channel). - Hooks run synchronously by default; wrap with
AsyncHookfor fire-and-forget.
Always call lg.Close() before exit to flush async buffers and close files.
For os.Exit paths (panic recovery, fatal errors), call Close in your
shutdown hook or use a defer.
MIT