36. Structured logging and observability: log/slog, metrics, tracing
36.1 log/slog (standard library since Go 1.21)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("request handled",
"method", r.Method,
"path", r.URL.Path,
"status", status,
"latency_ms", latency.Milliseconds(),
)
// {"time":"...","level":"INFO","msg":"request handled","method":"GET",...}
Structured (key-value, machine-parseable) logs replace fmt.Printf-style
free text specifically so log aggregators (Loki, Elasticsearch,
CloudWatch Insights) can filter and query on fields instead of grepping
strings.
36.2 Metrics: counters, gauges, histograms
import "github.com/prometheus/client_golang/prometheus"
var requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{Name: "http_request_duration_seconds"},
[]string{"method", "path", "status"},
)
requestDuration.WithLabelValues(r.Method, r.URL.Path, status).Observe(latency.Seconds())
36.3 Tracing: propagating a request ID/span across services
Combine the context.WithValue request-ID pattern from Chapter 18 with
log/slog's attribute passing, and every log line for a given request —
across every service it touches — carries the same ID, turning "find
every log line for this one failed request across five microservices"
from a manual grep exercise into a single query.
36.4 Real-world example
The single highest-leverage observability change most young Go services
are missing is: structured logs with a request ID, runtime.NumGoroutine()
as a gauge (Chapter 15's war story), and net/http/pprof mounted on an
internal-only port (Chapter 20) — three cheap additions that turn "why is
prod slow" from a guessing game into a five-minute diagnosis.
Applied in the series: the HTTPS guide's production server logs every request with log/slog, and its SLO chapter turns request outcomes into burn-rate alerts.