forked from matoszz/echo-prometheus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
middleware.go
128 lines (110 loc) · 2.92 KB
/
middleware.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package echoprometheus
import (
"net/http"
"strconv"
echo "github.com/datumforge/echox"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
const (
httpRequestsCount = "requests_total"
httpRequestsDuration = "request_duration_seconds"
notFoundPath = "/not-found"
)
// Config responsible to configure middleware
type Config struct {
Namespace string
Buckets []float64
Subsystem string
NormalizeHTTPStatus bool
}
// DefaultConfig has the default instrumentation config
var DefaultConfig = Config{
Namespace: "echo",
Subsystem: "http",
Buckets: []float64{
0.0005,
0.001, // 1ms
0.002,
0.005,
0.01, // 10ms
0.02,
0.05,
0.1, // 100 ms
0.2,
0.5,
1.0, // 1s
2.0,
5.0,
10.0, // 10s
15.0,
20.0,
30.0,
},
NormalizeHTTPStatus: true,
}
func normalizeHTTPStatus(statusCode int) string {
switch {
case statusCode < http.StatusOK:
return "1xx"
case statusCode < http.StatusMultipleChoices:
return "2xx"
case statusCode < http.StatusBadRequest:
return "3xx"
case statusCode < http.StatusInternalServerError:
return "4xx"
default:
return "5xx"
}
}
func isNotFoundHandler(path string) bool {
return path == echo.RouteNotFound
}
// NewConfig returns a new config with default values
func NewConfig() Config {
return DefaultConfig
}
// MetricsMiddleware returns an echo middleware with default config for instrumentation.
func MetricsMiddleware() echo.MiddlewareFunc {
return MetricsMiddlewareWithConfig(DefaultConfig)
}
// MetricsMiddlewareWithConfig returns an echo middleware for instrumentation.
func MetricsMiddlewareWithConfig(config Config) echo.MiddlewareFunc {
httpRequests := promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: config.Namespace,
Subsystem: config.Subsystem,
Name: httpRequestsCount,
Help: "Number of HTTP operations",
}, []string{"status", "method", "handler"})
httpDuration := promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: config.Namespace,
Subsystem: config.Subsystem,
Name: httpRequestsDuration,
Help: "Spend time by processing a route",
Buckets: config.Buckets,
}, []string{"method", "handler"})
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(context echo.Context) error {
request := context.Request()
path := context.Path()
// to avoid attack high cardinality of 404
if isNotFoundHandler(context.Path()) {
path = notFoundPath
}
timer := prometheus.NewTimer(httpDuration.WithLabelValues(request.Method, path))
err := next(context)
timer.ObserveDuration()
if err != nil {
context.Error(err)
}
status := ""
if config.NormalizeHTTPStatus {
status = normalizeHTTPStatus(context.Response().Status)
} else {
status = strconv.Itoa(context.Response().Status)
}
httpRequests.WithLabelValues(status, request.Method, path).Inc()
return err
}
}
}