-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandlers.go
executable file
·169 lines (147 loc) · 4.29 KB
/
handlers.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package handlers
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"time"
"github.com/prometheus/client_golang/prometheus"
)
// Common interfaces and types
// Handler is a custom handler type, not made for HTTP but rather for Go only when using gqlgen
type Handler func(ctx context.Context, w Writer, r Reader) error
// Writer provides an agnostic output from the handler methods
type Writer interface {
io.Writer
}
// Reader will let request structs come in in a generic form
type Reader interface {
io.Reader
}
// MustRegisterMetrics for prometheus
func MustRegisterMetrics(namespace, subsystem string) (*prometheus.HistogramVec, *prometheus.GaugeVec, *prometheus.CounterVec, *prometheus.CounterVec) {
metricDuration := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Help: "Duration histogram of time taken to execute requests",
Name: "request_duration_milliseconds",
}, []string{"method"})
metricSaturation := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Help: "Saturation levels",
Name: "in_flight_total",
}, []string{"method"})
metricCalls := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Help: "Number of calls",
Name: "calls_total",
}, []string{"method"})
metricErrors := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Help: "Number of errors",
Name: "errors_total",
}, []string{"method"})
prometheus.MustRegister(metricDuration)
prometheus.MustRegister(metricCalls)
prometheus.MustRegister(metricSaturation)
prometheus.MustRegister(metricErrors)
return metricDuration, metricSaturation, metricCalls, metricErrors
}
// UserError holds input and validation errors
type UserError struct {
ID string
Message string
Err error
}
func (e *UserError) Error() string {
return e.Message
}
// SystemError holds internal system errors
type SystemError struct {
ID string
Message string
Err error
}
func (e *SystemError) Error() string {
return e.Message
}
// Middlewares
// WithAdmin prevents downstream execution if the user in context is not admin
func WithAdmin(next Handler) Handler {
fn := func(ctx context.Context, w Writer, r Reader) error {
// Check context for admin
isAdmin, ok := ctx.Value("is_admin").(bool)
if !isAdmin {
return &UserError{Message: "user not admin"}
}
if !ok {
return &SystemError{Message: "is_admin not in context"}
}
return next(ctx, w, r)
}
return fn
}
// WithMetrics will execute relevant prometheus values
func WithMetrics(method string,
calls *prometheus.CounterVec,
errors *prometheus.CounterVec,
saturation *prometheus.GaugeVec,
duration *prometheus.HistogramVec,
next Handler) Handler {
fn := func(ctx context.Context, w Writer, r Reader) error {
// Handle metrics
start := time.Now()
calls.WithLabelValues(method).Inc()
saturation.WithLabelValues(method).Inc()
err := next(ctx, w, r)
if err != nil {
errors.WithLabelValues(method).Inc()
}
saturation.WithLabelValues(method).Dec()
duration.WithLabelValues(method).Observe(float64(time.Since(start).Nanoseconds()) / 1000000)
return err
}
return fn
}
// WithLogging will add start, end and duration logs
func WithLogging(next Handler) Handler {
fn := func(ctx context.Context, w Writer, r Reader) error {
// Handle logging
now := time.Now()
err := next(ctx, w, r)
since := time.Since(now)
fmt.Println(since.Nanoseconds())
return err
}
return fn
}
// Helper funcs
// MustEncode will write to w the JSON encoded value
// It will panic on error
func MustEncode(w io.Writer, v interface{}) {
err := json.NewEncoder(w).Encode(v)
if err != nil {
panic(err)
}
}
// MustDecode read from r and hydrate v
// It will panic on error
func MustDecode(r io.Reader, v interface{}) {
err := json.NewDecoder(r).Decode(v)
if err != nil {
panic(err)
}
}
// MustNewReader return a Reader of v
// It will panic on error
func MustNewReader(v interface{}) io.Reader {
b, err := json.Marshal(v)
if err != nil {
panic(err)
}
return bytes.NewReader(b)
}