Logging
log/slog (Go 1.21) provides structured logging with JSON output for production and human-readable text output for development - attach request-scoped fields with slog.With.
log/slog (introduced in Go 1.21) is the standard structured logging package. It replaces ad-hoc log.Printf calls with key-value pairs that log aggregators can index and query.
slog.Info, slog.Error, slog.Debug, and slog.Warn are the four level functions. Each accepts a message and alternating key-value pairs. The default handler writes human-readable text to stderr.
package main
import (
"log/slog"
"os"
)
func main() {
slog.Info("server starting", "port", 8080, "env", "production")
slog.Debug("cache miss", "key", "user:42") // filtered if level > Debug
slog.Warn("rate limit approaching", "remaining", 10)
slog.Error("database unreachable", "host", "db.internal", "err", os.ErrDeadlineExceeded)
}Switch to a JSON handler for production. Use slog.With to create a child logger with pre-attached fields - every log line emitted from that logger includes those fields automatically.
package main
import (
"log/slog"
"os"
)
func main() {
// JSON handler for structured output (log aggregators expect this)
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
// Attach request-scoped fields once; every subsequent log includes them
reqLogger := logger.With("request_id", "abc-123", "user_id", 42)
reqLogger.Info("request received", "method", "GET", "path", "/api/users")
reqLogger.Info("request complete", "status", 200, "duration_ms", 14)
}In production
Use log/slog for all new code - it is the stdlib answer to zerolog and zap for most workloads. Structured logs (JSON) are required for log aggregators like Datadog, CloudWatch Logs, and Grafana Loki; a log.Printf string line is effectively unsearchable at scale. Use slog.With to attach request-scoped fields (trace ID, user ID, tenant ID) at the handler entry point so every log line in a request includes them without threading the logger through every function call. Set the minimum level via slog.HandlerOptions.Level - debug logs in production are a compliance risk (they may contain PII or secrets) and a throughput risk (log volume can exceed ingestion limits under load). For high-throughput services where slog's allocation overhead is measurable, zerolog or zap remain valid choices, but profile first. Never log sensitive fields (passwords, tokens, full credit card numbers) at any level; redact at the point of construction.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free