package metrics import ( "net/http" "strconv" "time" ) // responseWriter wraps http.ResponseWriter to capture the status code written // by downstream handlers. type responseWriter struct { http.ResponseWriter status int } func (rw *responseWriter) WriteHeader(code int) { rw.status = code rw.ResponseWriter.WriteHeader(code) } // statusCode returns the captured status code, defaulting to 200 if WriteHeader // was never called (Go's default behaviour). func (rw *responseWriter) statusCode() string { if rw.status == 0 { return "200" } return strconv.Itoa(rw.status) } // Middleware records request_count, request_duration, and request_errors for // every HTTP request. It must be added to the chi middleware chain after // RequestID but before the route handlers. func Middleware(provider string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() wrapped := &responseWriter{ResponseWriter: w} next.ServeHTTP(wrapped, r) status := wrapped.statusCode() duration := time.Since(start).Seconds() path := r.URL.Path RequestsTotal.WithLabelValues(r.Method, path, status, provider).Inc() RequestDuration.WithLabelValues(r.Method, path, status).Observe(duration) // Count 4xx/5xx as errors. code := wrapped.status if code == 0 { code = http.StatusOK } if code >= 400 { errorType := "server_error" if code == http.StatusUnauthorized { errorType = "authentication_error" } else if code == http.StatusTooManyRequests { errorType = "rate_limit_error" } else if code < 500 { errorType = "client_error" } RequestErrors.WithLabelValues(r.Method, path, errorType).Inc() } }) } }