-
-
Notifications
You must be signed in to change notification settings - Fork 109
/
metrics.go
145 lines (127 loc) · 4.58 KB
/
metrics.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
package prometheus
import (
"net/http"
"strconv"
"github.com/ory/x/httpx"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Metrics prototypes
type Metrics struct {
responseTime *prometheus.HistogramVec
totalRequests *prometheus.CounterVec
duration *prometheus.HistogramVec
responseSize *prometheus.HistogramVec
requestSize *prometheus.HistogramVec
handlerStatuses *prometheus.CounterVec
}
const HTTPMetrics = "http"
const GRPCMetrics = "grpc"
// NewMetrics creates new custom Prometheus metrics
func NewMetrics(app, metricsPrefix, version, hash, date string) *Metrics {
labels := map[string]string{
"app": app,
"version": version,
"hash": hash,
"buildTime": date,
}
if metricsPrefix != "" {
metricsPrefix += "_"
}
pm := &Metrics{
responseTime: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: metricsPrefix + "response_time_seconds",
Help: "Description",
ConstLabels: labels,
},
[]string{"endpoint"},
),
totalRequests: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: metricsPrefix + "requests_total",
Help: "number of requests",
ConstLabels: labels,
}, []string{"code", "method", "endpoint"}),
duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: metricsPrefix + "requests_duration_seconds",
Help: "duration of a requests in seconds",
ConstLabels: labels,
}, []string{"code", "method", "endpoint"}),
responseSize: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: metricsPrefix + "response_size_bytes",
Help: "size of the responses in bytes",
ConstLabels: labels,
}, []string{"code", "method"}),
requestSize: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: metricsPrefix + "requests_size_bytes",
Help: "size of the requests in bytes",
ConstLabels: labels,
}, []string{"code", "method"}),
handlerStatuses: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: metricsPrefix + "requests_statuses_total",
Help: "count number of responses per status",
ConstLabels: labels,
}, []string{"method", "status_bucket"}),
}
err := prometheus.Register(pm)
if e := new(prometheus.AlreadyRegisteredError); errors.As(err, e) {
return pm
} else if err != nil {
panic(err)
}
return pm
}
// Describe implements prometheus Collector interface.
func (h *Metrics) Describe(in chan<- *prometheus.Desc) {
h.duration.Describe(in)
h.totalRequests.Describe(in)
h.requestSize.Describe(in)
h.responseSize.Describe(in)
h.handlerStatuses.Describe(in)
h.responseTime.Describe(in)
}
// Collect implements prometheus Collector interface.
func (h *Metrics) Collect(in chan<- prometheus.Metric) {
h.duration.Collect(in)
h.totalRequests.Collect(in)
h.requestSize.Collect(in)
h.responseSize.Collect(in)
h.handlerStatuses.Collect(in)
h.responseTime.Collect(in)
}
func (h Metrics) instrumentHandlerStatusBucket(next http.Handler) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
next.ServeHTTP(rw, r)
status, _ := httpx.GetResponseMeta(rw)
statusBucket := "unknown"
switch {
case status >= 200 && status <= 299:
statusBucket = "2xx"
case status >= 300 && status <= 399:
statusBucket = "3xx"
case status >= 400 && status <= 499:
statusBucket = "4xx"
case status >= 500 && status <= 599:
statusBucket = "5xx"
}
h.handlerStatuses.With(prometheus.Labels{"method": r.Method, "status_bucket": statusBucket}).
Inc()
}
}
// Instrument will instrument any http.HandlerFunc with custom metrics
func (h Metrics) Instrument(rw http.ResponseWriter, next http.HandlerFunc, endpoint string) http.HandlerFunc {
labels := prometheus.Labels{}
labelsWithEndpoint := prometheus.Labels{"endpoint": endpoint}
if status, _ := httpx.GetResponseMeta(rw); status != 0 {
labels = prometheus.Labels{"code": strconv.Itoa(status)}
labelsWithEndpoint["code"] = labels["code"]
}
wrapped := promhttp.InstrumentHandlerResponseSize(h.responseSize.MustCurryWith(labels), next)
wrapped = promhttp.InstrumentHandlerCounter(h.totalRequests.MustCurryWith(labelsWithEndpoint), wrapped)
wrapped = promhttp.InstrumentHandlerDuration(h.duration.MustCurryWith(labelsWithEndpoint), wrapped)
wrapped = promhttp.InstrumentHandlerDuration(h.responseTime.MustCurryWith(prometheus.Labels{"endpoint": endpoint}), wrapped)
wrapped = promhttp.InstrumentHandlerRequestSize(h.requestSize.MustCurryWith(labels), wrapped)
wrapped = h.instrumentHandlerStatusBucket(wrapped)
return wrapped.ServeHTTP
}