Structured Logging with slog
Go 1.21 added log/slog to the standard library โ a structured, levelled logger designed to produce machine-parseable output while still being readable by humans.
Why structured logging?
Structured logs let you filter across millions of events with queries like userID == 42 instead of regex scraping.
Basic usage
The default logger writes to stderr in a human-readable format:
Each call takes a message followed by alternating key/value pairs.
Typed attributes
For performance-sensitive paths, use explicitly typed attributes to avoid reflection:
slog.String, slog.Int, slog.Duration, slog.Any โ these create slog.Attr values that carry type information without boxing.
Creating a custom logger
slog.New takes a Handler โ the component that formats and writes log records:
Pass a *slog.HandlerOptions as the second argument to customise the minimum level:
Log levels
| Level | Value | Use when |
|---|---|---|
slog.LevelDebug | -4 | Fine-grained diagnostics |
slog.LevelInfo | 0 | Normal operation events |
slog.LevelWarn | 4 | Unexpected but recoverable |
slog.LevelError | 8 | Errors that need attention |
The default logger's level is Info โ Debug messages are dropped unless you set a lower level.
Setting the default logger
Replace the global default logger with your custom one:
Adding shared context with With
logger.With returns a new logger that attaches fixed attributes to every subsequent log call โ perfect for request-scoped fields:
Knowledge Check
What is the key advantage of structured logging over fmt.Printf / log.Printf?
Which slog handler produces output suitable for log aggregators like Datadog or Splunk?
What does logger.With(...) return?