forked from globocom/echo-prometheus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmiddleware.go
150 lines (128 loc) · 3.49 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
// Package echoprometheus implements prometheus metrics middleware
package echoprometheus
import (
"reflect"
"strconv"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// Config responsible to configure middleware
type Config struct {
HandlerLabelMappingFunc func(c echo.Context) string
Skipper middleware.Skipper
Namespace string
Subsystem string
Buckets []float64
NormalizeHTTPStatus bool
}
// DefaultHandlerLabelMappingFunc returns the handler path
func DefaultHandlerLabelMappingFunc(c echo.Context) string {
return c.Path()
}
// DefaultSkipper doesn't skip anything
func DefaultSkipper(c echo.Context) bool {
return false
}
const (
httpRequestsCount = "requests_total"
httpRequestsDuration = "request_duration_seconds"
notFoundPath = "/not-found"
)
// 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,
Skipper: DefaultSkipper,
HandlerLabelMappingFunc: DefaultHandlerLabelMappingFunc,
}
// nolint: gomnd
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"
}
func isNotFoundHandler(handler echo.HandlerFunc) bool {
return reflect.ValueOf(handler).Pointer() == reflect.ValueOf(echo.NotFoundHandler).Pointer()
}
// 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(c echo.Context) error {
req := c.Request()
path := config.HandlerLabelMappingFunc(c)
// to avoid attack high cardinality of 404
if isNotFoundHandler(c.Handler()) {
path = notFoundPath
}
begin := time.Now()
err := next(c)
dur := time.Since(begin)
if err != nil {
c.Error(err)
}
if config.Skipper(c) {
return nil
}
httpDuration.WithLabelValues(req.Method, path).Observe(dur.Seconds())
status := ""
if config.NormalizeHTTPStatus {
status = normalizeHTTPStatus(c.Response().Status)
} else {
status = strconv.Itoa(c.Response().Status)
}
httpRequests.WithLabelValues(status, req.Method, path).Inc()
return err
}
}
}