Part 4: Instrumenting Go Services — Metrics, Traces, and Logs
The Observability Deficit
Prometheus Instrumentation
The Metrics Middleware
// internal/gateway/middleware/metrics.go
package middleware
import (
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: "goreliable",
Subsystem: "api_gateway",
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "route", "status_code"},
)
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "goreliable",
Subsystem: "api_gateway",
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
// Buckets chosen to cover the range from fast cached responses to slow DB queries
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5},
},
[]string{"method", "route", "status_code"},
)
httpRequestsInFlight = promauto.NewGauge(
prometheus.GaugeOpts{
Namespace: "goreliable",
Subsystem: "api_gateway",
Name: "http_requests_in_flight",
Help: "Number of HTTP requests currently being processed",
},
)
)
// responseWriter wraps http.ResponseWriter to capture the status code
type responseWriter struct {
http.ResponseWriter
status int
written bool
}
func (rw *responseWriter) WriteHeader(code int) {
if !rw.written {
rw.status = code
rw.written = true
rw.ResponseWriter.WriteHeader(code)
}
}
func Metrics(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
httpRequestsInFlight.Inc()
defer httpRequestsInFlight.Dec()
rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(rw, r)
// Use chi's route pattern (e.g., /api/v1/orders/{id}) not the actual path
// This prevents high cardinality from individual order IDs becoming metric labels
route := chi.RouteContext(r.Context()).RoutePattern()
if route == "" {
route = "unknown"
}
labels := prometheus.Labels{
"method": r.Method,
"route": route,
"status_code": strconv.Itoa(rw.status),
}
httpRequestsTotal.With(labels).Inc()
httpRequestDuration.With(labels).Observe(time.Since(start).Seconds())
})
}Order Service Metrics
Notification Worker Metrics
OpenTelemetry Distributed Tracing
Setup
Tracing in the API Gateway
Propagating Traces to the Order Service
Order Service Span
Structured Logging with Trace Correlation
Deploying the Observability Stack via ArgoCD
OpenTelemetry Collector Configuration
Grafana Dashboard
What the Three Pillars Now Give Me
PreviousPart 3: GitOps with ArgoCD and Continuous DeliveryNextPart 5: SLIs, SLOs, and Error Budgets in Practice
Last updated