+99
util/svcutil/metrics_middleware.go
+99
util/svcutil/metrics_middleware.go
···
1
+
package svcutil
2
+
3
+
import (
4
+
"errors"
5
+
"net/http"
6
+
"strconv"
7
+
"time"
8
+
9
+
"github.com/labstack/echo/v4"
10
+
"github.com/prometheus/client_golang/prometheus"
11
+
"github.com/prometheus/client_golang/prometheus/promauto"
12
+
)
13
+
14
+
var reqSz = promauto.NewHistogramVec(prometheus.HistogramOpts{
15
+
Name: "http_request_size_bytes",
16
+
Help: "A histogram of request sizes for requests.",
17
+
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
18
+
}, []string{"code", "method", "path"})
19
+
20
+
var reqDur = promauto.NewHistogramVec(prometheus.HistogramOpts{
21
+
Name: "http_request_duration_seconds",
22
+
Help: "A histogram of latencies for requests.",
23
+
Buckets: prometheus.ExponentialBuckets(0.001, 2, 15),
24
+
}, []string{"code", "method", "path"})
25
+
26
+
var reqCnt = promauto.NewCounterVec(prometheus.CounterOpts{
27
+
Name: "http_requests_total",
28
+
Help: "A counter for requests to the wrapped handler.",
29
+
}, []string{"code", "method", "path"})
30
+
31
+
var resSz = promauto.NewHistogramVec(prometheus.HistogramOpts{
32
+
Name: "http_response_size_bytes",
33
+
Help: "A histogram of response sizes for requests.",
34
+
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
35
+
}, []string{"code", "method", "path"})
36
+
37
+
// MetricsMiddleware defines handler function for metrics middleware
38
+
func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
39
+
return func(c echo.Context) error {
40
+
path := c.Path()
41
+
if path == "/metrics" || path == "/_health" {
42
+
return next(c)
43
+
}
44
+
45
+
start := time.Now()
46
+
requestSize := computeApproximateRequestSize(c.Request())
47
+
48
+
err := next(c)
49
+
50
+
status := c.Response().Status
51
+
if err != nil {
52
+
var httpError *echo.HTTPError
53
+
if errors.As(err, &httpError) {
54
+
status = httpError.Code
55
+
}
56
+
if status == 0 || status == http.StatusOK {
57
+
status = http.StatusInternalServerError
58
+
}
59
+
}
60
+
61
+
elapsed := float64(time.Since(start)) / float64(time.Second)
62
+
63
+
statusStr := strconv.Itoa(status)
64
+
method := c.Request().Method
65
+
66
+
responseSize := float64(c.Response().Size)
67
+
68
+
reqDur.WithLabelValues(statusStr, method, path).Observe(elapsed)
69
+
reqCnt.WithLabelValues(statusStr, method, path).Inc()
70
+
reqSz.WithLabelValues(statusStr, method, path).Observe(float64(requestSize))
71
+
resSz.WithLabelValues(statusStr, method, path).Observe(responseSize)
72
+
73
+
return err
74
+
}
75
+
}
76
+
77
+
func computeApproximateRequestSize(r *http.Request) int {
78
+
s := 0
79
+
if r.URL != nil {
80
+
s = len(r.URL.Path)
81
+
}
82
+
83
+
s += len(r.Method)
84
+
s += len(r.Proto)
85
+
for name, values := range r.Header {
86
+
s += len(name)
87
+
for _, value := range values {
88
+
s += len(value)
89
+
}
90
+
}
91
+
s += len(r.Host)
92
+
93
+
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
94
+
95
+
if r.ContentLength != -1 {
96
+
s += int(r.ContentLength)
97
+
}
98
+
return s
99
+
}