-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathhealth.go
143 lines (122 loc) · 3.38 KB
/
health.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
package healthcheck
import (
"context"
"encoding/json"
"errors"
"net/http"
"sync"
"time"
)
type response struct {
Status string `json:"status,omitempty"`
Errors map[string]string `json:"errors,omitempty"`
}
type health struct {
checkers map[string]Checker
observers map[string]Checker
timeout time.Duration
}
// Checker checks the status of the dependency and returns error.
// In case the dependency is working as expected, return nil.
type Checker interface {
Check(ctx context.Context) error
}
// CheckerFunc is a convenience type to create functions that implement the Checker interface.
type CheckerFunc func(ctx context.Context) error
// Check Implements the Checker interface to allow for any func() error method
// to be passed as a Checker
func (c CheckerFunc) Check(ctx context.Context) error {
return c(ctx)
}
// Handler returns an http.Handler
func Handler(opts ...Option) http.Handler {
h := &health{
checkers: make(map[string]Checker),
observers: make(map[string]Checker),
timeout: 30 * time.Second,
}
for _, opt := range opts {
opt(h)
}
return h
}
// HandlerFunc returns an http.HandlerFunc to mount the API implementation at a specific route
func HandlerFunc(opts ...Option) http.HandlerFunc {
return Handler(opts...).ServeHTTP
}
// Option adds optional parameter for the HealthcheckHandlerFunc
type Option func(*health)
// WithChecker adds a status checker that needs to be added as part of healthcheck. i.e database, cache or any external dependency
func WithChecker(name string, s Checker) Option {
return func(h *health) {
h.checkers[name] = &timeoutChecker{s}
}
}
// WithObserver adds a status checker but it does not fail the entire status.
func WithObserver(name string, s Checker) Option {
return func(h *health) {
h.observers[name] = &timeoutChecker{s}
}
}
// WithTimeout configures the global timeout for all individual checkers.
func WithTimeout(timeout time.Duration) Option {
return func(h *health) {
h.timeout = timeout
}
}
func (h *health) ServeHTTP(w http.ResponseWriter, r *http.Request) {
nCheckers := len(h.checkers) + len(h.observers)
code := http.StatusOK
errorMsgs := make(map[string]string, nCheckers)
ctx, cancel := context.Background(), func() {}
if h.timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, h.timeout)
}
defer cancel()
var mutex sync.Mutex
var wg sync.WaitGroup
wg.Add(nCheckers)
for key, checker := range h.checkers {
go func(key string, checker Checker) {
if err := checker.Check(ctx); err != nil {
mutex.Lock()
errorMsgs[key] = err.Error()
code = http.StatusServiceUnavailable
mutex.Unlock()
}
wg.Done()
}(key, checker)
}
for key, observer := range h.observers {
go func(key string, observer Checker) {
if err := observer.Check(ctx); err != nil {
mutex.Lock()
errorMsgs[key] = err.Error()
mutex.Unlock()
}
wg.Done()
}(key, observer)
}
wg.Wait()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
json.NewEncoder(w).Encode(response{
Status: http.StatusText(code),
Errors: errorMsgs,
})
}
type timeoutChecker struct {
checker Checker
}
func (t *timeoutChecker) Check(ctx context.Context) error {
checkerChan := make(chan error)
go func() {
checkerChan <- t.checker.Check(ctx)
}()
select {
case err := <-checkerChan:
return err
case <-ctx.Done():
return errors.New("max check time exceeded")
}
}