log/slog

Guided tour · Errors & Logging · pkg.go.dev →

Structured logging (Go 1.21+). Leveled, attributable, and produces text or JSON for free.

The default logger

Info, Warn, Error — with key/value attrs

slog.Info("request served",
    "method", "GET",
    "path", "/api/users",
    "status", 200,
)
Output
2024/03/14 15:09:26 INFO request served method=GET path=/api/users status=200

Debug, Warn, Error

Debug is silenced by default (level INFO). Set Level to change that.

slog.Debug("cache miss", "key", k)
slog.Warn("slow query", "ms", took.Milliseconds())
slog.Error("db write failed", "err", err)

Handlers — where logs go and how they look

A Logger wraps a Handler. Swap the handler for JSON output or to filter levels.

JSONHandler — machine-readable

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("served", "status", 200, "ms", 42)
Output
{"time":"2024-03-14T15:09:26Z","level":"INFO","msg":"served","status":200,"ms":42}

Set the default logger for the whole program

slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
})))

Attributes and groups

Typed attrs with slog.Any / slog.Int / slog.String

Prefer typed attrs in hot paths: no reflection, no allocations for many common cases.

slog.LogAttrs(ctx, slog.LevelInfo, "order placed",
    slog.String("id", orderID),
    slog.Int("items", n),
    slog.Duration("took", d),
)

With — derive a logger with pinned fields

reqLogger := slog.Default().With("req_id", id, "user", u)
reqLogger.Info("auth ok")
reqLogger.Info("db read", "table", "users")

Group — nested attributes

logger.Info("user signed up",
    slog.Group("user",
        slog.String("email", email),
        slog.String("plan", "pro"),
    ),
)