67 lines
1.8 KiB
Go
67 lines
1.8 KiB
Go
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()
|
|
}
|
|
})
|
|
}
|
|
}
|