Go

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 !BADKEY attr (go vet catches it). Prefer slog.Int, etc.
  • With pre-formats attributes once — cheaper than repeating them.
  • Expensive values: check l.Enabled(ctx, level) or implement LogValuer so the work is lazy.
  • Pass the *slog.Logger explicitly or via context; custom handlers can pull trace IDs from ctx in Handle.

More on Standard Library, HTTP & Systems Design in Go

All 35 Standard Library, HTTP & Systems Design in Go questions