forked from globocom/echo-prometheus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmiddleware.go
154 lines (133 loc) · 3.52 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package echoprometheus
import (
"reflect"
"strconv"
"github.com./prometheus/client_golang/prometheus"
"github.com./webx-top/echo"
)
// Config responsible to configure middleware
type Config struct {
Skipper echo.Skipper
Namespace string
Buckets []float64
Subsystem string
NormalizeHTTPStatus bool
OnlyRoutePath bool
}
const (
httpRequestsCount = "requests_total"
httpRequestsDuration = "request_duration_seconds"
notFoundPath = "/not-found"
)
// DefaultConfig has the default instrumentation config
var DefaultConfig = Config{
Skipper: func(c echo.Context) bool {
return c.Request().URL().Path() == c.Echo().Prefix()+`/metrics`
},
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,
OnlyRoutePath: true,
}
func normalizeHTTPStatus(status int) string {
if status < 200 {
return "1xx"
} else if status < 300 {
return "2xx"
} else if status < 400 {
return "3xx"
} else if status < 500 {
return "4xx"
}
return "5xx"
}
var notFoundHandlerPointer = reflect.ValueOf(echo.NotFoundHandler).Pointer()
func isNotFoundHandler(handler echo.Handler) bool {
return reflect.ValueOf(handler).Pointer() == notFoundHandlerPointer
}
// 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.MiddlewareFuncd {
return MetricsMiddlewareWithConfig(DefaultConfig)
}
// MetricsMiddlewareWithConfig returns an echo middleware for instrumentation.
func MetricsMiddlewareWithConfig(config Config) echo.MiddlewareFuncd {
httpRequests := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: config.Namespace,
Subsystem: config.Subsystem,
Name: httpRequestsCount,
Help: "Number of HTTP operations",
}, []string{"status", "method", "handler"})
prometheus.DefaultRegisterer.Unregister(httpRequests)
prometheus.DefaultRegisterer.MustRegister(httpRequests)
httpDuration := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: config.Namespace,
Subsystem: config.Subsystem,
Name: httpRequestsDuration,
Help: "Spend time by processing a route",
Buckets: config.Buckets,
}, []string{"method", "handler"})
prometheus.DefaultRegisterer.Unregister(httpDuration)
prometheus.DefaultRegisterer.MustRegister(httpDuration)
skipper := config.Skipper
if skipper == nil {
skipper = echo.DefaultSkipper
}
return func(next echo.Handler) echo.HandlerFunc {
return func(c echo.Context) error {
if skipper(c) {
return next.Handle(c)
}
req := c.Request()
var path string
// to avoid attack high cardinality of 404
if isNotFoundHandler(next) {
path = notFoundPath
}
if len(path) == 0 {
if config.OnlyRoutePath {
path = c.Path()
} else {
path = req.URL().Path()
}
}
//c.Logger().Debug(path)
timer := prometheus.NewTimer(httpDuration.WithLabelValues(req.Method(), path))
err := next.Handle(c)
timer.ObserveDuration()
if err != nil {
c.Error(err)
}
var status string
if config.NormalizeHTTPStatus {
status = normalizeHTTPStatus(c.Response().Status())
} else {
status = strconv.Itoa(c.Response().Status())
}
httpRequests.WithLabelValues(status, req.Method(), path).Inc()
return err
}
}
}