How does log/slog work? Discuss handlers, attributes, LogValuer, and performance.
Question 462MediumGo 1.22 to 1.25
log/slog (Go 1.21) separates the front-end *slog.Logger from a pluggable slog.Handler (TextHandler, JSONHandler, or third-party). Records have a time, level, message and key-value Attrs.
var level = new(slog.LevelVar) // change level at runtime
func setup() *slog.Logger {
level.Set(slog.LevelInfo)
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level, AddSource: true})
l := slog.New(h).With("service", "orders", "version", "1.4.2")
slog.SetDefault(l)
return l
}
type Token string
// LogValue redacts secrets anywhere they're logged.
func (Token) LogValue() slog.Value { return slog.StringValue("REDACTED") }
func handle(ctx context.Context, l *slog.Logger, id int, tok Token) {
reqLog := l.With(slog.Group("req", slog.Int("id", id)))
reqLog.InfoContext(ctx, "processing", "token", tok)
// Fast path: typed attrs, no interface boxing of pairs.
reqLog.LogAttrs(ctx, slog.LevelDebug, "detail", slog.Duration("elapsed", time.Second))
}
Gotchas and talking points:
- Alternating key-value args are unchecked: an odd count yields a
!BADKEYattr (go vetcatches it). Preferslog.Int, etc. Withpre-formats attributes once — cheaper than repeating them.- Expensive values: check
l.Enabled(ctx, level)or implementLogValuerso the work is lazy. - Pass the
*slog.Loggerexplicitly or via context; custom handlers can pull trace IDs fromctxinHandle.
More on Standard Library, HTTP & Systems Design in Go
- Q460What are the pitfalls of bufio.Scanner and bufio.Writer?
- Q461Implement graceful shutdown for an HTTP server with background workers.
- Q463What does this print? Explain the monotonic clock and time.Time comparison.
- Q464time.Ticker, time.After and timers: what leaks, and what changed in Go 1.23?
- Q465What does this print? (time.Duration arithmetic gotchas)
- Q466Explain the laws of reflection and settability. What does this print?