-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathprometheus.go
148 lines (130 loc) · 3.92 KB
/
prometheus.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
package fasthttpprom
import (
"fmt"
"log"
"strconv"
"time"
"github.com/fasthttp/router"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
)
var defaultMetricPath = "/metrics"
// ListenerHandler url label
type ListenerHandler func(c *fasthttp.RequestCtx) string
// Prometheus contains the metrics gathered by the instance and its path
type Prometheus struct {
reqDur *prometheus.HistogramVec
router *router.Router
listenAddress string
MetricsPath string
Handler fasthttp.RequestHandler
}
// NewPrometheus generates a new set of metrics with a certain subsystem name
func NewPrometheus(subsystem string) *Prometheus {
p := &Prometheus{
MetricsPath: defaultMetricPath,
}
p.registerMetrics(subsystem)
return p
}
// SetListenAddress for exposing metrics on address. If not set, it will be exposed at the
// same address of api that is being used
func (p *Prometheus) SetListenAddress(address string) {
p.listenAddress = address
if p.listenAddress != "" {
p.router = router.New()
}
}
// SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of
// your content's access log).
func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *router.Router) {
p.listenAddress = listenAddress
if len(p.listenAddress) > 0 {
p.router = r
}
}
// SetMetricsPath set metrics paths for Custom path
func (p *Prometheus) SetMetricsPath(r *router.Router) {
if p.listenAddress != "" {
r.GET(p.MetricsPath, prometheusHandler())
p.runServer()
} else {
r.GET(p.MetricsPath, prometheusHandler())
}
}
func (p *Prometheus) runServer() {
if p.listenAddress != "" {
go fasthttp.ListenAndServe(p.listenAddress, p.router.Handler)
}
}
func (p *Prometheus) registerMetrics(subsystem string) {
p.reqDur = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Subsystem: subsystem,
Name: "request_duration_seconds",
Help: "request latencies",
Buckets: []float64{.005, .01, .02, 0.04, .06, 0.08, .1, 0.15, .25, 0.4, .6, .8, 1, 1.5, 2, 3, 5},
},
[]string{"code", "path"},
)
prometheus.Register(p.reqDur)
}
// Custom adds the middleware to a fasthttp
func (p *Prometheus) Custom(r *router.Router) {
p.router = r
p.SetMetricsPath(r)
p.Handler = p.HandlerFunc()
}
// Use adds the middleware to a fasthttp
func (p *Prometheus) Use(r *router.Router) {
p.router = r
r.GET(p.MetricsPath, prometheusHandler())
p.Handler = p.HandlerFunc()
}
// HandlerFunc is onion or wraper to handler for fasthttp listenandserve
func (p *Prometheus) HandlerFunc() fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
uri := string(ctx.Request.URI().Path())
if uri == p.MetricsPath {
// next
p.router.Handler(ctx)
return
}
start := time.Now()
// next
p.router.Handler(ctx)
status := strconv.Itoa(ctx.Response.StatusCode())
elapsed := float64(time.Since(start)) / float64(time.Second)
// get route pattern of url
routeList := p.router.List()
paths, ok := routeList[string(ctx.Request.Header.Method())]
handler, _ := p.router.Lookup(string(ctx.Request.Header.Method()), uri, ctx)
if ok {
for _, v := range paths {
tmp, _ := p.router.Lookup(string(ctx.Request.Header.Method()), v, ctx)
if fmt.Sprintf("%v", tmp) == fmt.Sprintf("%v", handler) {
uri = v
break
}
}
}
ep := ""
if status == "404" {
ep = "404_" + string(ctx.Method())
} else {
ep = string(ctx.Method()) + "_" + uri
}
ob, err := p.reqDur.GetMetricWithLabelValues(status, ep)
if err != nil {
log.Printf("Fail to GetMetricWithLabelValues: %s\n", err)
return
}
ob.Observe(elapsed)
}
}
// since prometheus/client_golang use net/http we need this net/http adapter for fasthttp
func prometheusHandler() fasthttp.RequestHandler {
return fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler())
}