Prometheus Go Code Review Review Checklist Metric types match measurement semantics (Counter/Gauge/Histogram) Labels have low cardinality (no user IDs, timestamps, paths) Metric names follow conventions (snake_case, unit suffix) Histograms use appropriate bucket boundaries Metrics registered once, not per-request Collectors don't panic on race conditions /metrics endpoint exposed and accessible Metric Type Selection Measurement Type Example Requests processed Counter requests_total Items in queue Gauge queue_length Request duration Histogram request_duration_seconds Concurrent connections Gauge active_connections Errors since start Counter errors_total Memory usage Gauge memory_bytes Critical Anti-Patterns 1. High Cardinality Labels // BAD - unique per user/request counter := promauto.NewCounterVec( prometheus.CounterOpts{Name: "requests_total"}, []string{"user_id", "path"}, // millions of series! ) counter.WithLabelValues(userID, request.URL.Path).Inc()
// GOOD - bounded label values counter := promauto.NewCounterVec( prometheus.CounterOpts{Name: "requests_total"}, []string{"method", "status_code"}, // <100 series ) counter.WithLabelValues(r.Method, statusCode).Inc()
- Wrong Metric Type // BAD - using gauge for monotonic value requestCount := promauto.NewGauge(prometheus.GaugeOpts{ Name: "http_requests", }) requestCount.Inc() // should be Counter!
// GOOD requestCount := promauto.NewCounter(prometheus.CounterOpts{ Name: "http_requests_total", }) requestCount.Inc()
- Registering Per-Request // BAD - new metric per request func handler(w http.ResponseWriter, r *http.Request) { counter := prometheus.NewCounter(...) // creates new each time! prometheus.MustRegister(counter) // panics on duplicate! }
// GOOD - register once var requestCounter = promauto.NewCounter(prometheus.CounterOpts{ Name: "http_requests_total", })
func handler(w http.ResponseWriter, r *http.Request) { requestCounter.Inc() }
- Missing Unit Suffix // BAD duration := promauto.NewHistogram(prometheus.HistogramOpts{ Name: "request_duration", // no unit! })
// GOOD duration := promauto.NewHistogram(prometheus.HistogramOpts{ Name: "request_duration_seconds", // unit in name })
Good Patterns Metric Definition var ( httpRequests = promauto.NewCounterVec( prometheus.CounterOpts{ Namespace: "myapp", Subsystem: "http", Name: "requests_total", Help: "Total HTTP requests processed", }, []string{"method", "status"}, )
httpDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "myapp",
Subsystem: "http",
Name: "request_duration_seconds",
Help: "HTTP request latencies",
Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
},
[]string{"method"},
)
)
Middleware Pattern func metricsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { timer := prometheus.NewTimer(httpDuration.WithLabelValues(r.Method)) defer timer.ObserveDuration()
wrapped := &responseWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(wrapped, r)
httpRequests.WithLabelValues(r.Method, strconv.Itoa(wrapped.status)).Inc()
})
}
Exposing Metrics import "github.com/prometheus/client_golang/prometheus/promhttp"
func main() { http.Handle("/metrics", promhttp.Handler()) http.ListenAndServe(":9090", nil) }
Review Questions Are metric types correct (Counter vs Gauge vs Histogram)? Are label values bounded (no UUIDs, timestamps, paths)? Do metric names include units (_seconds, _bytes)? Are metrics registered once (not per-request)? Is /metrics endpoint properly exposed?