mirror of
https://github.com/stefanprodan/podinfo.git
synced 2026-03-03 02:20:18 +00:00
96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package api
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
type PrometheusMiddleware struct {
|
|
Histogram *prometheus.HistogramVec
|
|
Counter *prometheus.CounterVec
|
|
}
|
|
|
|
func NewPrometheusMiddleware() *PrometheusMiddleware {
|
|
// used for monitoring and alerting (RED method)
|
|
histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
|
Subsystem: "http",
|
|
Name: "request_duration_seconds",
|
|
Help: "Seconds spent serving HTTP requests.",
|
|
Buckets: prometheus.DefBuckets,
|
|
}, []string{"method", "path", "status"})
|
|
// used for horizontal pod auto-scaling (Kubernetes HPA v2)
|
|
counter := prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Subsystem: "http",
|
|
Name: "requests_total",
|
|
Help: "The total number of HTTP requests.",
|
|
},
|
|
[]string{"status"},
|
|
)
|
|
|
|
prometheus.MustRegister(histogram)
|
|
prometheus.MustRegister(counter)
|
|
|
|
return &PrometheusMiddleware{
|
|
Histogram: histogram,
|
|
Counter: counter,
|
|
}
|
|
}
|
|
|
|
func (i *PrometheusMiddleware) Handler(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
begin := time.Now()
|
|
interceptor := &interceptor{ResponseWriter: w, statusCode: http.StatusOK}
|
|
path := urlToLabel(r.URL.Path)
|
|
next.ServeHTTP(interceptor, r)
|
|
var (
|
|
status = strconv.Itoa(interceptor.statusCode)
|
|
took = time.Since(begin)
|
|
)
|
|
i.Histogram.WithLabelValues(r.Method, path, status).Observe(took.Seconds())
|
|
i.Counter.WithLabelValues(status).Inc()
|
|
})
|
|
}
|
|
|
|
var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
|
|
|
// converts a URL path to a string compatible with Prometheus label value.
|
|
func urlToLabel(path string) string {
|
|
result := invalidChars.ReplaceAllString(path, "_")
|
|
result = strings.ToLower(strings.Trim(result, "_"))
|
|
if result == "" {
|
|
result = "root"
|
|
}
|
|
return result
|
|
}
|
|
|
|
type interceptor struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
recorded bool
|
|
}
|
|
|
|
func (i *interceptor) WriteHeader(code int) {
|
|
if !i.recorded {
|
|
i.statusCode = code
|
|
i.recorded = true
|
|
}
|
|
i.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
func (i *interceptor) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
hj, ok := i.ResponseWriter.(http.Hijacker)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("interceptor: can't cast parent ResponseWriter to Hijacker")
|
|
}
|
|
return hj.Hijack()
|
|
}
|