/
circuit_breaker_tripper.go
100 lines (86 loc) · 3.19 KB
/
circuit_breaker_tripper.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
package transport
import (
"errors"
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/sony/gobreaker"
)
// circuitBreakerTripper implements a ChainableRoundTripper with
// the circuit breaker "gobreaker". By default the circuit breaker
// opens if more than 5 consecutive calls to RoundTrip fail while
// it is closed. To change theses settings a gobreaker.Settings
// object can be passed to NewCircuitBreakerTripper(). For more
// information about the specifiable settings please visit:
// https://github.com/sony/gobreaker
//
// To keep track how often the circuit breaker tripped and
// transitioned to the open/half-open state, a prometheus counter
// is added to each newly instantiated circuit breaker.
//
// CAUTION: It is advised to use this RoundTripper as the last used
// RoundTripper before the actual request is triggered. Otherwise
// the circuit breaker might not open because of real connectivity
// problems, but because of other RoundTrippers.
type circuitBreakerTripper struct {
transport http.RoundTripper
breaker *gobreaker.CircuitBreaker
}
func NewDefaultCircuitBreakerTripper(name string) *circuitBreakerTripper {
return NewCircuitBreakerTripper(gobreaker.Settings{
Name: name,
})
}
func NewCircuitBreakerTripper(settings gobreaker.Settings) *circuitBreakerTripper {
if settings.Name == "" {
panic("name is mandatory for circuit breaker")
}
stateSwitchCounterVec := prometheus.NewCounterVec(prometheus.CounterOpts{
ConstLabels: prometheus.Labels{"name": settings.Name},
Name: "pace_http_circuit_breaker_state_switch_total",
Help: "help",
}, []string{"from", "to"})
var ok bool
var are prometheus.AlreadyRegisteredError
if err := prometheus.Register(stateSwitchCounterVec); errors.As(err, &are) {
stateSwitchCounterVec, ok = are.ExistingCollector.(*prometheus.CounterVec)
if !ok {
panic(fmt.Sprintf(`existing "pace_http_circuit_breaker_state_switch_total" collector no CounterVec, but %T`, are.ExistingCollector))
}
} else if err != nil {
panic(err)
}
handler := settings.OnStateChange
settings.OnStateChange = func(s string, from, to gobreaker.State) {
if handler != nil {
handler(s, from, to)
}
labels := prometheus.Labels{"from": from.String(), "to": to.String()}
stateSwitchCounterVec.With(labels).Inc()
}
return &circuitBreakerTripper{breaker: gobreaker.NewCircuitBreaker(settings)}
}
// Transport returns the RoundTripper to make HTTP requests
func (c *circuitBreakerTripper) Transport() http.RoundTripper {
return c.transport
}
// SetTransport sets the RoundTripper to make HTTP requests
func (c *circuitBreakerTripper) SetTransport(rt http.RoundTripper) {
c.transport = rt
}
// RoundTrip executes a single HTTP transaction via Transport()
func (c *circuitBreakerTripper) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := c.breaker.Execute(func() (interface{}, error) {
return c.transport.RoundTrip(req)
})
if err != nil {
switch {
case errors.Is(err, gobreaker.ErrOpenState):
// inform the caller about the broken circuit
return nil, fmt.Errorf("%w: considering host '%s' unreachable", ErrCircuitBroken, req.Host)
default:
return nil, err
}
}
return resp.(*http.Response), nil
}