forked from labbsr0x/mux-monitor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
monitor.go
144 lines (114 loc) · 4.57 KB
/
monitor.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
package gin_monitor
import (
"errors"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type Monitor struct {
reqDuration *prometheus.HistogramVec
dependencyReqDuration *prometheus.HistogramVec
respSize *prometheus.CounterVec
dependencyUP *prometheus.GaugeVec
applicationInfo *prometheus.GaugeVec
errorMessageKey string
IsStatusError func(statusCode int) bool
}
// DependencyStatus is the type to represent UP or DOWN states
type DependencyStatus int
// DependencyChecker specifies the methods a checker must implement.
type DependencyChecker interface {
GetDependencyName() string
Check() DependencyStatus
}
const (
DOWN DependencyStatus = iota
UP
)
const DefaultErrorMessageKey = "error-message"
var (
DefaultBuckets = []float64{0.1, 0.3, 1.5, 10.5}
)
//New create new Monitor instance
func New(applicationVersion string, errorMessageKey string, buckets []float64) (*Monitor, error) {
if strings.TrimSpace(applicationVersion) == "" {
return nil, errors.New("application version must be a non-empty string")
}
if strings.TrimSpace(applicationVersion) == "" {
errorMessageKey = DefaultErrorMessageKey
}
if buckets == nil {
buckets = DefaultBuckets
}
monitor := &Monitor{errorMessageKey: errorMessageKey, IsStatusError: IsStatusError}
monitor.reqDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "request_seconds",
Help: "Duration in seconds of HTTP requests.",
Buckets: buckets,
}, []string{"type", "status", "method", "addr", "isError", "errorMessage"})
monitor.respSize = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "response_size_bytes",
Help: "Counts the size of each HTTP response",
}, []string{"type", "status", "method", "addr", "isError", "errorMessage"})
monitor.dependencyUP = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "dependency_up",
Help: "Records if a dependency is up or down. 1 for up, 0 for down",
}, []string{"name"})
monitor.dependencyReqDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "dependency_request_seconds",
Help: "Duration of dependency requests in seconds.",
Buckets: buckets,
}, []string{"name", "type", "status", "method", "addr", "isError", "errorMessage"})
monitor.applicationInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "application_info",
Help: "Static information about the application",
}, []string{"version"})
monitor.applicationInfo.WithLabelValues(applicationVersion).Set(1)
return monitor, nil
}
func (m *Monitor) collectTime(reqType, status, method, addr, isError, errorMessage string, durationSeconds float64) {
m.reqDuration.WithLabelValues(reqType, status, method, addr, isError, errorMessage).Observe(durationSeconds)
}
func (m *Monitor) collectSize(reqType, status, method, addr, isError, errorMessage string, size float64) {
m.respSize.WithLabelValues(reqType, status, method, addr, isError, errorMessage).Add(size)
}
// CollectDependencyTime collet the duration of dependency requests in seconds
func (m *Monitor) CollectDependencyTime(name, reqType, status, method, addr, isError, errorMessage string, durationSeconds float64) {
m.dependencyReqDuration.WithLabelValues(name, reqType, status, method, addr, isError, errorMessage).Observe(durationSeconds)
}
// Prometheus implements mux.MiddlewareFunc.
func (m *Monitor) Prometheus() gin.HandlerFunc {
return func(c *gin.Context) {
w := c.Writer
r := c.Request
respWriter := NewResponseWriter(w)
path := r.URL.Path
c.Next()
duration := time.Since(respWriter.started)
statusCodeStr := respWriter.StatusCodeStr()
isErrorStr := strconv.FormatBool(m.IsStatusError(respWriter.statusCode))
errorMessage := r.Header.Get(m.errorMessageKey)
r.Header.Del(m.errorMessageKey)
m.collectTime(r.Proto, statusCodeStr, r.Method, path, isErrorStr, errorMessage, duration.Seconds())
m.collectSize(r.Proto, statusCodeStr, r.Method, path, isErrorStr, errorMessage, float64(respWriter.Count()))
}
}
// AddDependencyChecker creates a ticker that periodically executes the checker and collects the dependency state metrics
func (m *Monitor) AddDependencyChecker(checker DependencyChecker, checkingPeriod time.Duration) {
ticker := time.NewTicker(checkingPeriod)
go func() {
for {
select {
case <-ticker.C:
status := checker.Check()
m.dependencyUP.WithLabelValues(checker.GetDependencyName()).Set(float64(status))
}
}
}()
}
func IsStatusError(statusCode int) bool {
return statusCode < 200 || statusCode >= 400
}