Logging
Go 1.21 introduced log/slog — structured logging in the standard library.
Introduction
Go 1.21 introduced log/slog — structured logging in the standard library. Replace fmt.Println debugging with leveled logs (Debug, Info, Warn, Error) carrying key-value attributes queryable in ELK, Loki, and CloudWatch.
Production logging adds request correlation IDs via middleware, logs errors with stack context, and never logs secrets. The legacy log package still appears in older codebases — know both, prefer slog for new projects.
Interviewers ask structured vs unstructured logging, log levels, and correlation in distributed systems — this lesson implements slog with JSON handler for production output.
The story
When a Google SRE debugs a failing gRPC call at 2 AM, structured JSON logs with fields like {"service":"payments","trace_id":"abc","latency_ms":842} let them filter in Datadog instantly. Go 1.21+ log/slog replaces ad-hoc fmt.Printf with leveled, structured logging — the standard for Kubernetes operators and microservices.
Log at appropriate levels: DEBUG for development, INFO for business events, ERROR for failures with stack context — never log secrets or PII in production.
Understanding the topic
Key concepts
- slog.Info(msg, key, val, key, val) structured key-value pairs.
- slog.NewJSONHandler writes JSON lines to io.Writer.
- Log levels: Debug, Info, Warn, Error — filter by minimum level.
- slog.WithGroup and With attrs add persistent context.
- Context-scoped logger via middleware stored in context.
- Never log passwords, tokens, PAN, or full PII.
sequenceDiagramClient->>Server: HTTP RequestServer->>Handler: ServeHTTPHandler->>Service: Business LogicService->>DB: QueryDB-->>Handler: DataHandler-->>Client: JSON Response
Step-by-step explanation
- Create handler: slog.NewJSONHandler(os.Stdout, opts).
- Default logger: slog.SetDefault(slog.New(handler)).
- Middleware extracts/generates request ID, adds to logger.
- Handler logs request start/end with duration and status.
- Error logs include err attribute and operation context.
- Production ships JSON logs to centralized aggregation.
Practical code example
Structured logging with slog JSON handler and request middleware:
package mainimport ("log/slog""net/http""os""time")func loggingMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {start := time.Now()reqID := r.Header.Get("X-Request-ID")if reqID == "" {reqID = "gen-" + time.Now().Format("150405")}logger := slog.With("request_id", reqID, "method", r.Method, "path", r.URL.Path)logger.Info("request started")next.ServeHTTP(w, r)logger.Info("request completed", "duration_ms", time.Since(start).Milliseconds())})}func main() {handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})slog.SetDefault(slog.New(handler))mux := http.NewServeMux()mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("ok"))})slog.Info("server starting", "port", 8080)http.ListenAndServe(":8080", loggingMiddleware(mux))}
Line-by-line code explanation
slog.Info("request handled", "method", r.Method, "path", r.URL.Path)logs structured key-value pairs.slog.Error("db query failed", "err", err)records errors with context fields.logger := slog.With("service", "payments")creates a child logger with bound fields.slog.NewJSONHandler(os.Stdout, opts)emits JSON lines for log aggregators like Loki or CloudWatch.slog.SetDefault(logger)configures the global logger for packages usingslog.Infodirectly.log/slog levels: Debug, Info, Warn, Error— filter by level in production.context-aware logging— attach trace IDs from context values in middleware.avoid fmt.Sprintf in hot paths— structured handlers defer formatting until the level is enabled.
Key takeaway: JSON logs parse in Loki/ELK. Add request_id for distributed tracing correlation. Set Level Debug in dev, Info in prod.
Real-world use
Where you'll use this in production
- Centralized logging in Kubernetes with Fluent Bit sidecar.
- Audit trails for financial transaction services.
- Debugging production incidents with request ID search.
- SLI/SLO monitoring from structured log metrics.
Best practices
- Use slog structured attributes, not fmt.Sprintf in logs.
- Consistent field names: request_id, user_id, duration_ms.
- Log level appropriate: Error for failures, Info for business events.
- Redact sensitive fields before logging.
- One log line per event — avoid multi-line unstructured dumps.
- Integrate with OpenTelemetry for logs+traces correlation.
Common mistakes
- Logging inside tight loops at Info level — noise and cost.
- String interpolation instead of structured fields.
- Logging full request bodies with passwords.
- No correlation ID — impossible to trace distributed requests.
- Debug logs left on in production overwhelming storage.
Advanced interview questions
Q1Beginnerslog vs log package?
Q2BeginnerWhy structured logging?
Q3IntermediateCorrelation ID pattern?
Q4IntermediateLogs vs metrics vs traces?
Q5AdvancedLogging for PCI-compliant payment service.
Summary
log/slog provides structured leveled logging in stdlib. JSON handler outputs machine-parseable production logs. Middleware adds request_id for correlation. Never log secrets; redact PII consistently. Next lesson: dependency injection patterns.