mirror of
https://github.com/marcusDenslow/terminalShop.git
synced 2026-10-25 02:52:20 +02:00
feature: expose /metrics with prometheus client_golang
Adds Counter (http_requests_total) and Histogram (http_request_duration_seconds) labeled by method, chi route pattern, and status. Wired through a new Metrics middleware (after Recovery so panics-turned-500s are still counted) and an unauthenticated /metrics endpoint at the root for Prometheus to scrape. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
57
api/middleware/metrics.go
Normal file
57
api/middleware/metrics.go
Normal file
@@ -0,0 +1,57 @@
|
||||
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"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
httpRequestsTotal = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "http_requests_total",
|
||||
Help: "Total HTTP requests processed, labeled by method, route, and status",
|
||||
},
|
||||
[]string{"method", "route", "status"},
|
||||
)
|
||||
|
||||
httpRequestDuration = promauto.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "http_request_duration_seconds",
|
||||
Help: "HTTP request latency in seconds, labeled by method and route",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"method", "route"},
|
||||
)
|
||||
)
|
||||
|
||||
// Metrics records request count and latency for every request
|
||||
func Metrics(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
wrapped := newResponseWriter(w)
|
||||
|
||||
next.ServeHTTP(wrapped, r)
|
||||
|
||||
route := chi.RouteContext(r.Context()).RoutePattern()
|
||||
if route == "" {
|
||||
route = "unknown"
|
||||
}
|
||||
|
||||
duration := time.Since(start).Seconds()
|
||||
status := strconv.Itoa(wrapped.statusCode)
|
||||
|
||||
httpRequestsTotal.WithLabelValues(r.Method, route, status).Inc()
|
||||
httpRequestDuration.WithLabelValues(r.Method, route).Observe(duration)
|
||||
})
|
||||
}
|
||||
|
||||
// MetricsHandler returns the Prometheus scrape endpoint
|
||||
func MetricsHandler() http.Handler {
|
||||
return promhttp.Handler()
|
||||
}
|
||||
@@ -29,6 +29,7 @@ func SetupRoutes(
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.CORS())
|
||||
r.Use(middleware.Recovery)
|
||||
r.Use(middleware.Metrics)
|
||||
r.Use(middleware.Auth(jwtManager))
|
||||
// Broad IP-level rate limit: 200 req/min per IP across all endpoints.
|
||||
r.Use(middleware.RateLimitByIP(200, time.Minute))
|
||||
@@ -50,6 +51,7 @@ func SetupRoutes(
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte(`<!DOCTYPE html><html><body style="font-family:monospace;text-align:center;padding:4rem"><h2>Card added.</h2><p>You can close this tab.</p></body></html>`))
|
||||
})
|
||||
r.Handle("/metrics", middleware.MetricsHandler())
|
||||
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
// Health — accept HEAD for cheap probes from monitoring tools.
|
||||
|
||||
Reference in New Issue
Block a user