Instrument HTTP requests¶
Metrics.HTTPMiddleware records the RED signals — Rate, Errors, Duration — for
every HTTP request, on the same registry that serves /metrics. Labels use the
route template, never the raw URL, so cardinality stays bounded.
m, err := metrics.New(metrics.WithMiddleware(authMiddleware))
if err != nil { return err }
httpMetrics, err := m.HTTPMiddleware()
if err != nil { return err }
mux := http.NewServeMux()
mux.Handle("GET /users/{id}", httpMetrics(usersHandler)) // instrument a route
_ = m.MountOn(mux) // and expose /metrics
Or wire it once across the whole server through go/transport's middleware chain:
srv, _ := transporthttp.NewServer(ctx, settings, mux,
transporthttp.WithMiddleware(transithttp.NewChain(httpMetrics)))
The metrics produced¶
| Metric | Type | Labels |
|---|---|---|
http_requests_total |
counter | method, route, status |
http_request_duration_seconds |
histogram | method, route, status |
http_requests_in_flight |
gauge | — |
route is the matched template (GET /users/{id}), read from http.Request.Pattern
(Go 1.22+ ServeMux). /users/42 and /users/43 share one series.
Options¶
| Option | Effect |
|---|---|
WithHTTPNamespace("myapp") |
prefix the metric names (myapp_http_requests_total) |
WithDurationBuckets(...) |
override the histogram buckets (default prometheus.DefBuckets) |
WithStatusClass() |
record status as its class (2xx) instead of the exact code |
WithRouteLabelFunc(f) |
derive the route template yourself (non-stdlib routers) |
WithUnmatchedRouteLabel("<other>") |
the bucket for requests that matched no route |
WithHTTPConstLabels{...} |
stamp constant labels (e.g. version) on the HTTP metrics |
Non-stdlib routers¶
If you don't use http.ServeMux, r.Pattern is empty and everything would fall into
the <other> bucket. Supply WithRouteLabelFunc to read your router's matched route
template — chi's chi.RouteContext(r.Context()).RoutePattern(), gin's c.FullPath(),
etc. Return the template, never the raw path — see
Cardinality safety.
Errors and duration¶
The status label captures the response code (via a lightweight ResponseWriter
wrapper that preserves flushing/hijacking through http.ResponseController), so
error rate is sum(rate(http_requests_total{status=~"5.."}[5m])). The histogram
gives you latency quantiles per route. Add exemplars to jump from a
latency spike straight to the trace.