67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
package httpapi
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
"strconv"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
httpRequests = promauto.NewCounterVec(prometheus.CounterOpts{
|
||
|
|
Namespace: "voice_platform", Subsystem: "http", Name: "requests_total",
|
||
|
|
Help: "Total HTTP requests by service, method, route and status.",
|
||
|
|
}, []string{"service", "method", "route", "status"})
|
||
|
|
httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||
|
|
Namespace: "voice_platform", Subsystem: "http", Name: "request_duration_seconds",
|
||
|
|
Help: "HTTP request duration by service, method and route.",
|
||
|
|
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5},
|
||
|
|
}, []string{"service", "method", "route"})
|
||
|
|
httpInflight = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||
|
|
Namespace: "voice_platform", Subsystem: "http", Name: "inflight_requests",
|
||
|
|
Help: "Current in-flight HTTP requests by service.",
|
||
|
|
}, []string{"service"})
|
||
|
|
)
|
||
|
|
|
||
|
|
func MetricsHandler() http.Handler { return promhttp.Handler() }
|
||
|
|
|
||
|
|
func metricsMiddleware(serviceName string, next http.Handler) http.Handler {
|
||
|
|
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
startedAt := time.Now()
|
||
|
|
httpInflight.WithLabelValues(serviceName).Inc()
|
||
|
|
defer httpInflight.WithLabelValues(serviceName).Dec()
|
||
|
|
recorder := &statusRecorder{ResponseWriter: writer, status: http.StatusOK}
|
||
|
|
defer func() {
|
||
|
|
route := request.Pattern
|
||
|
|
if route == "" {
|
||
|
|
route = "unmatched"
|
||
|
|
}
|
||
|
|
httpRequests.WithLabelValues(serviceName, request.Method, route, strconv.Itoa(recorder.status)).Inc()
|
||
|
|
httpDuration.WithLabelValues(serviceName, request.Method, route).Observe(time.Since(startedAt).Seconds())
|
||
|
|
}()
|
||
|
|
next.ServeHTTP(recorder, request)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
type statusRecorder struct {
|
||
|
|
http.ResponseWriter
|
||
|
|
status int
|
||
|
|
bytes int
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *statusRecorder) WriteHeader(status int) {
|
||
|
|
r.status = status
|
||
|
|
r.ResponseWriter.WriteHeader(status)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *statusRecorder) Write(body []byte) (int, error) {
|
||
|
|
written, err := r.ResponseWriter.Write(body)
|
||
|
|
r.bytes += written
|
||
|
|
return written, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (r *statusRecorder) Unwrap() http.ResponseWriter { return r.ResponseWriter }
|