stoplight 3.0.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +174 -181
- data/lib/stoplight/builder.rb +70 -0
- data/lib/stoplight/circuit_breaker.rb +102 -0
- data/lib/stoplight/configurable.rb +95 -0
- data/lib/stoplight/configuration.rb +126 -0
- data/lib/stoplight/data_store/memory.rb +20 -5
- data/lib/stoplight/data_store/redis.rb +37 -5
- data/lib/stoplight/default.rb +2 -0
- data/lib/stoplight/error.rb +1 -0
- data/lib/stoplight/light/deprecated.rb +44 -0
- data/lib/stoplight/light/lockable.rb +45 -0
- data/lib/stoplight/light/runnable.rb +31 -13
- data/lib/stoplight/light.rb +69 -63
- data/lib/stoplight/rspec/generic_notifier.rb +42 -0
- data/lib/stoplight/rspec.rb +3 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight.rb +32 -8
- data/spec/spec_helper.rb +7 -0
- data/spec/stoplight/builder_spec.rb +165 -0
- data/spec/stoplight/circuit_breaker_spec.rb +43 -0
- data/spec/stoplight/configurable_spec.rb +25 -0
- data/spec/stoplight/data_store/memory_spec.rb +12 -149
- data/spec/stoplight/data_store/redis_spec.rb +26 -158
- data/spec/stoplight/error_spec.rb +10 -0
- data/spec/stoplight/light/lockable_spec.rb +93 -0
- data/spec/stoplight/light/runnable_spec.rb +14 -265
- data/spec/stoplight/light_spec.rb +4 -28
- data/spec/stoplight/notifier/generic_spec.rb +35 -35
- data/spec/stoplight/notifier/io_spec.rb +1 -0
- data/spec/stoplight/notifier/logger_spec.rb +3 -0
- data/spec/stoplight_spec.rb +17 -6
- data/spec/support/configurable.rb +69 -0
- data/spec/support/data_store/base/clear_failures.rb +18 -0
- data/spec/support/data_store/base/clear_state.rb +20 -0
- data/spec/support/data_store/base/get_all.rb +44 -0
- data/spec/support/data_store/base/get_failures.rb +30 -0
- data/spec/support/data_store/base/get_state.rb +7 -0
- data/spec/support/data_store/base/names.rb +29 -0
- data/spec/support/data_store/base/record_failures.rb +70 -0
- data/spec/support/data_store/base/set_state.rb +15 -0
- data/spec/support/data_store/base/with_notification_lock.rb +27 -0
- data/spec/support/data_store/base.rb +21 -0
- data/spec/support/database_cleaner.rb +26 -0
- data/spec/support/exception_helpers.rb +9 -0
- data/spec/support/light/runnable/color.rb +79 -0
- data/spec/support/light/runnable/run.rb +247 -0
- data/spec/support/light/runnable/state.rb +31 -0
- data/spec/support/light/runnable.rb +5 -0
- metadata +53 -231
- data/lib/stoplight/notifier/bugsnag.rb +0 -37
- data/lib/stoplight/notifier/honeybadger.rb +0 -44
- data/lib/stoplight/notifier/pagerduty.rb +0 -21
- data/lib/stoplight/notifier/raven.rb +0 -40
- data/lib/stoplight/notifier/rollbar.rb +0 -39
- data/lib/stoplight/notifier/slack.rb +0 -21
- data/spec/stoplight/notifier/bugsnag_spec.rb +0 -90
- data/spec/stoplight/notifier/honeybadger_spec.rb +0 -88
- data/spec/stoplight/notifier/pagerduty_spec.rb +0 -40
- data/spec/stoplight/notifier/raven_spec.rb +0 -90
- data/spec/stoplight/notifier/rollbar_spec.rb +0 -90
- data/spec/stoplight/notifier/slack_spec.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26a32d4e3aa3c515fb23a75f3055d250eaf10431eeae1316086bfe69ca569277
|
4
|
+
data.tar.gz: 9cfadd0dc4bbc99687aeffc847ae68b9926b29383530a2d55ae79ac2b22039b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ee973ed322845425185904de1d681064794d3a39e7d303c36b172b377d080541867e81df5cfc2a1a75da07b1088dcf15de61c807d446ca3d60582c1d645d07f
|
7
|
+
data.tar.gz: 16420c055193f5c9364d517ecea8d4ffb1ad9fbd184a77f792d1b693ab13b5d9040f56b53027c1cfcc53b971590cb7c4ffb215c2cf3692e6c978bb8f51d592b3
|
data/README.md
CHANGED
@@ -9,7 +9,6 @@ Stoplight is traffic control for code. It's an implementation of the circuit
|
|
9
9
|
breaker pattern in Ruby.
|
10
10
|
|
11
11
|
---
|
12
|
-
|
13
12
|
Does your code use unreliable systems, like a flaky database or a spotty web
|
14
13
|
service? Wrap calls to those up in stoplights to prevent them from affecting
|
15
14
|
the rest of your application.
|
@@ -17,97 +16,88 @@ the rest of your application.
|
|
17
16
|
Check out [stoplight-admin][] for controlling your stoplights.
|
18
17
|
|
19
18
|
- [Installation](#installation)
|
20
|
-
- [Basic
|
21
|
-
- [Custom
|
22
|
-
- [Custom
|
23
|
-
- [Custom
|
24
|
-
- [Custom
|
19
|
+
- [Basic Usage](#basic-usage)
|
20
|
+
- [Custom Errors](#custom-errors)
|
21
|
+
- [Custom Fallback](#custom-fallback)
|
22
|
+
- [Custom Threshold](#custom-threshold)
|
23
|
+
- [Custom Window Size](#custom-window-size)
|
24
|
+
- [Custom Cool Off Time](#custom-cool-off-time)
|
25
25
|
- [Rails](#rails)
|
26
26
|
- [Setup](#setup)
|
27
|
-
- [Data
|
27
|
+
- [Data Store](#data-store)
|
28
28
|
- [Redis](#redis)
|
29
29
|
- [Notifiers](#notifiers)
|
30
|
-
- [
|
31
|
-
- [Honeybadger](#honeybadger)
|
30
|
+
- [IO](#io)
|
32
31
|
- [Logger](#logger)
|
33
|
-
- [
|
34
|
-
- [
|
35
|
-
- [Sentry](#sentry)
|
36
|
-
- [Slack](#slack)
|
32
|
+
- [Community-supported Notifiers](#community-supported-notifiers)
|
33
|
+
- [How to Implement Your Own Notifier?](#how-to-implement-your-own-notifier)
|
37
34
|
- [Rails](#rails-1)
|
38
|
-
- [Advanced
|
35
|
+
- [Advanced Usage](#advanced-usage)
|
39
36
|
- [Locking](#locking)
|
40
37
|
- [Testing](#testing)
|
38
|
+
- [Maintenance Policy](#maintenance-policy)
|
41
39
|
- [Credits](#credits)
|
42
40
|
|
43
41
|
## Installation
|
44
42
|
|
45
43
|
Add it to your Gemfile:
|
46
44
|
|
47
|
-
```
|
45
|
+
```ruby
|
48
46
|
gem 'stoplight'
|
49
47
|
```
|
50
48
|
|
51
49
|
Or install it manually:
|
52
50
|
|
53
|
-
```
|
51
|
+
```sh
|
54
52
|
$ gem install stoplight
|
55
53
|
```
|
56
54
|
|
57
55
|
Stoplight uses [Semantic Versioning][]. Check out [the change log][] for a
|
58
56
|
detailed list of changes.
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
## Basic usage
|
58
|
+
## Basic Usage
|
63
59
|
|
64
60
|
To get started, create a stoplight:
|
65
61
|
|
66
|
-
```
|
67
|
-
light = Stoplight('example-pi')
|
68
|
-
# => #<Stoplight::Light:...>
|
62
|
+
```ruby
|
63
|
+
light = Stoplight('example-pi')
|
69
64
|
```
|
70
65
|
|
71
|
-
Then you can run it and it will return the result of calling the block. This is
|
72
|
-
the green state. (The green state corresponds to the closed state for circuit
|
73
|
-
breakers.)
|
66
|
+
Then you can run it with a block of code and it will return the result of calling the block. This is
|
67
|
+
the green state. (The green state corresponds to the closed state for circuit breakers.)
|
74
68
|
|
75
|
-
```
|
76
|
-
light.run
|
69
|
+
```ruby
|
70
|
+
light.run { 22.0 / 7 }
|
77
71
|
# => 3.142857142857143
|
78
72
|
light.color
|
79
73
|
# => "green"
|
80
74
|
```
|
81
75
|
|
82
76
|
If everything goes well, you shouldn't even be able to tell that you're using a
|
83
|
-
stoplight. That's not very interesting though, so let's
|
84
|
-
stoplight:
|
85
|
-
|
86
|
-
``` rb
|
87
|
-
light = Stoplight('example-zero') { 1 / 0 }
|
88
|
-
# => #<Stoplight::Light:...>
|
89
|
-
```
|
77
|
+
stoplight. That's not very interesting though, so let's make stoplight fail.
|
90
78
|
|
91
|
-
|
79
|
+
When you run it, the error will be recorded and passed through. After
|
92
80
|
running it a few times, the stoplight will stop trying and fail fast. This is
|
93
81
|
the red state. (The red state corresponds to the open state for circuit
|
94
|
-
|
82
|
+
breakers.)
|
95
83
|
|
96
|
-
```
|
97
|
-
light
|
84
|
+
```ruby
|
85
|
+
light = Stoplight('example-zero')
|
86
|
+
# => #<Stoplight::CircuitBreaker:...>
|
87
|
+
light.run { 1 / 0 }
|
98
88
|
# ZeroDivisionError: divided by 0
|
99
|
-
light.run
|
89
|
+
light.run { 1 / 0 }
|
100
90
|
# ZeroDivisionError: divided by 0
|
101
|
-
light.run
|
91
|
+
light.run { 1 / 0 }
|
102
92
|
# Switching example-zero from green to red because ZeroDivisionError divided by 0
|
103
93
|
# ZeroDivisionError: divided by 0
|
104
|
-
light.run
|
94
|
+
light.run { 1 / 0 }
|
105
95
|
# Stoplight::Error::RedLight: example-zero
|
106
96
|
light.color
|
107
97
|
# => "red"
|
108
98
|
```
|
109
99
|
|
110
|
-
When the
|
100
|
+
When the Stoplight changes from green to red, it will notify every configured
|
111
101
|
notifier. See [the notifiers section][] to learn more about notifiers.
|
112
102
|
|
113
103
|
The stoplight will move into the yellow state after being in the red state for
|
@@ -117,7 +107,7 @@ check out [the cool off time section][] When stoplights are yellow, they will
|
|
117
107
|
try to run their code. If it fails, they'll switch back to red. If it succeeds,
|
118
108
|
they'll switch to green.
|
119
109
|
|
120
|
-
### Custom
|
110
|
+
### Custom Errors
|
121
111
|
|
122
112
|
Some errors shouldn't cause your stoplight to move into the red state. Usually
|
123
113
|
these are handled elsewhere in your stack and don't represent real failures. A
|
@@ -140,89 +130,126 @@ provide a custom block that will be called with the error and a handler
|
|
140
130
|
never ignore the error. That means a `NoMemoryError` could change the color
|
141
131
|
of your stoplights.
|
142
132
|
|
143
|
-
```
|
144
|
-
light = Stoplight('example-not-found')
|
133
|
+
```ruby
|
134
|
+
light = Stoplight('example-not-found')
|
145
135
|
.with_error_handler do |error, handle|
|
146
|
-
|
147
|
-
|
136
|
+
if error.is_a?(ActiveRecord::RecordNotFound)
|
137
|
+
raise error
|
138
|
+
else
|
139
|
+
handle.call(error)
|
140
|
+
end
|
148
141
|
end
|
149
|
-
# => #<Stoplight::
|
150
|
-
light.run
|
142
|
+
# => #<Stoplight::CircuitBreaker:...>
|
143
|
+
light.run { User.find(123) }
|
151
144
|
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
152
|
-
light.run
|
145
|
+
light.run { User.find(123) }
|
153
146
|
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
154
|
-
light.run
|
147
|
+
light.run { User.find(123) }
|
155
148
|
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
156
149
|
light.color
|
157
150
|
# => "green"
|
158
151
|
```
|
159
152
|
|
160
|
-
### Custom
|
153
|
+
### Custom Fallback
|
161
154
|
|
162
155
|
By default, stoplights will re-raise errors when they're green. When they're
|
163
156
|
red, they'll raise a `Stoplight::Error::RedLight` error. You can provide a
|
164
157
|
fallback that will be called in both of these cases. It will be passed the
|
165
158
|
error if the light was green.
|
166
159
|
|
167
|
-
```
|
168
|
-
light = Stoplight('example-fallback')
|
160
|
+
```ruby
|
161
|
+
light = Stoplight('example-fallback')
|
169
162
|
.with_fallback { |e| p e; 'default' }
|
170
|
-
# => #<Stoplight::
|
171
|
-
light.run
|
163
|
+
# => #<Stoplight::CircuitBreaker:..>
|
164
|
+
light.run { 1 / 0 }
|
172
165
|
# #<ZeroDivisionError: divided by 0>
|
173
166
|
# => "default"
|
174
|
-
light.run
|
167
|
+
light.run { 1 / 0 }
|
175
168
|
# #<ZeroDivisionError: divided by 0>
|
176
169
|
# => "default"
|
177
|
-
light.run
|
170
|
+
light.run { 1 / 0 }
|
178
171
|
# Switching example-fallback from green to red because ZeroDivisionError divided by 0
|
179
172
|
# #<ZeroDivisionError: divided by 0>
|
180
173
|
# => "default"
|
181
|
-
light.run
|
174
|
+
light.run { 1 / 0 }
|
182
175
|
# nil
|
183
176
|
# => "default"
|
184
177
|
```
|
185
178
|
|
186
|
-
### Custom
|
179
|
+
### Custom Threshold
|
187
180
|
|
188
181
|
Some bits of code might be allowed to fail more or less frequently than others.
|
189
182
|
You can configure this by setting a custom threshold.
|
190
183
|
|
191
|
-
```
|
192
|
-
light = Stoplight('example-threshold')
|
184
|
+
```ruby
|
185
|
+
light = Stoplight('example-threshold')
|
193
186
|
.with_threshold(1)
|
194
|
-
# => #<Stoplight::
|
195
|
-
light.run
|
187
|
+
# => #<Stoplight::CircuitBreaker:...>
|
188
|
+
light.run { fail }
|
196
189
|
# Switching example-threshold from green to red because RuntimeError
|
197
190
|
# RuntimeError:
|
198
|
-
light.run
|
191
|
+
light.run { fail }
|
199
192
|
# Stoplight::Error::RedLight: example-threshold
|
200
193
|
```
|
201
194
|
|
202
195
|
The default threshold is `3`.
|
203
196
|
|
204
|
-
### Custom
|
197
|
+
### Custom Window Size
|
198
|
+
|
199
|
+
By default, all recorded failures, regardless of the time these happen, will count to reach
|
200
|
+
the threshold (hence turning the light to red). If needed, a window size can be set,
|
201
|
+
meaning you can control how many errors per period of time will count to reach the red
|
202
|
+
state.
|
203
|
+
|
204
|
+
By default, every recorded failure contributes to reaching the threshold, regardless of when it occurs,
|
205
|
+
causing the stoplight to turn red. By configuring a custom window size, you control how errors are
|
206
|
+
counted within a specified time frame. Here's how it works:
|
207
|
+
|
208
|
+
Let's say you set the window size to 2 seconds:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
window_size_in_seconds = 2
|
212
|
+
|
213
|
+
light = Stoplight('example-threshold')
|
214
|
+
.with_window_size(window_size_in_seconds)
|
215
|
+
.with_threshold(1) #=> #<Stoplight::CircuitBreaker:...>
|
216
|
+
|
217
|
+
light.run { 1 / 0 } #=> #<ZeroDivisionError: divided by 0>
|
218
|
+
sleep(3)
|
219
|
+
light.run { 1 / 0 }
|
220
|
+
```
|
221
|
+
|
222
|
+
Without the window size configuration, the second `light.run { 1 / 0 }` call will result in a
|
223
|
+
`Stoplight::Error::RedLight` exception being raised, as the stoplight transitions to the red state
|
224
|
+
after the first call. With a sliding window of 2 seconds, only the errors that occur within the latest
|
225
|
+
2 seconds are considered. The first error causes the stoplight to turn red, but after 3 seconds
|
226
|
+
(when the second error occurs), the window has shifted, and the stoplight switches to green state
|
227
|
+
causing the error to raise again. This provides a way to focus on the most recent errors.
|
228
|
+
|
229
|
+
The default window size is infinity, so all failures counts.
|
230
|
+
|
231
|
+
### Custom Cool Off Time
|
205
232
|
|
206
233
|
Stoplights will automatically attempt to recover after a certain amount of
|
207
234
|
time. A light in the red state for longer than the cool off period will
|
208
235
|
transition to the yellow state. This cool off time is customizable.
|
209
236
|
|
210
|
-
```
|
211
|
-
light = Stoplight('example-cool-off')
|
237
|
+
```ruby
|
238
|
+
light = Stoplight('example-cool-off')
|
212
239
|
.with_cool_off_time(1)
|
213
|
-
# => #<Stoplight::
|
214
|
-
light.run
|
240
|
+
# => #<Stoplight::CircuitBreaker:...>
|
241
|
+
light.run { fail }
|
215
242
|
# RuntimeError:
|
216
|
-
light.run
|
243
|
+
light.run { fail }
|
217
244
|
# RuntimeError:
|
218
|
-
light.run
|
245
|
+
light.run { fail }
|
219
246
|
# Switching example-cool-off from green to red because RuntimeError
|
220
247
|
# RuntimeError:
|
221
248
|
sleep(1)
|
222
249
|
# => 1
|
223
250
|
light.color
|
224
251
|
# => "yellow"
|
225
|
-
light.run
|
252
|
+
light.run { fail }
|
226
253
|
# RuntimeError:
|
227
254
|
```
|
228
255
|
|
@@ -236,19 +263,19 @@ effectively replaces the red state with yellow.
|
|
236
263
|
Stoplight was designed to wrap Rails actions with minimal effort. Here's an
|
237
264
|
example configuration:
|
238
265
|
|
239
|
-
```
|
266
|
+
```ruby
|
240
267
|
class ApplicationController < ActionController::Base
|
241
268
|
around_action :stoplight
|
242
269
|
|
243
270
|
private
|
244
271
|
|
245
272
|
def stoplight(&block)
|
246
|
-
Stoplight("#{params[:controller]}##{params[:action]}"
|
273
|
+
Stoplight("#{params[:controller]}##{params[:action]}")
|
247
274
|
.with_fallback do |error|
|
248
275
|
Rails.logger.error(error)
|
249
276
|
render(nothing: true, status: :service_unavailable)
|
250
277
|
end
|
251
|
-
.run
|
278
|
+
.run(&block)
|
252
279
|
end
|
253
280
|
end
|
254
281
|
```
|
@@ -259,10 +286,10 @@ end
|
|
259
286
|
|
260
287
|
Stoplight uses an in-memory data store out of the box.
|
261
288
|
|
262
|
-
```
|
289
|
+
```ruby
|
263
290
|
require 'stoplight'
|
264
291
|
# => true
|
265
|
-
Stoplight
|
292
|
+
Stoplight.default_data_store
|
266
293
|
# => #<Stoplight::DataStore::Memory:...>
|
267
294
|
```
|
268
295
|
|
@@ -271,17 +298,16 @@ the only supported persistent data store is Redis.
|
|
271
298
|
|
272
299
|
#### Redis
|
273
300
|
|
274
|
-
Make sure you have [the Redis gem][]
|
275
|
-
Stoplight.
|
301
|
+
Make sure you have [the Redis gem][] installed before configuring Stoplight.
|
276
302
|
|
277
|
-
```
|
303
|
+
```ruby
|
278
304
|
require 'redis'
|
279
305
|
# => true
|
280
306
|
redis = Redis.new
|
281
307
|
# => #<Redis client ...>
|
282
308
|
data_store = Stoplight::DataStore::Redis.new(redis)
|
283
309
|
# => #<Stoplight::DataStore::Redis:...>
|
284
|
-
Stoplight
|
310
|
+
Stoplight.default_data_store = data_store
|
285
311
|
# => #<Stoplight::DataStore::Redis:...>
|
286
312
|
```
|
287
313
|
|
@@ -290,38 +316,26 @@ Stoplight::Light.default_data_store = data_store
|
|
290
316
|
Stoplight sends notifications to standard error by default.
|
291
317
|
|
292
318
|
``` rb
|
293
|
-
Stoplight
|
319
|
+
Stoplight.default_notifiers
|
294
320
|
# => [#<Stoplight::Notifier::IO:...>]
|
295
321
|
```
|
296
322
|
|
297
323
|
If you want to send notifications elsewhere, you'll have to set them up.
|
298
324
|
|
299
|
-
####
|
300
|
-
|
301
|
-
Make sure you have [the Bugsnag gem][] (`~> 4.0`) installed before configuring
|
302
|
-
Stoplight.
|
303
|
-
|
304
|
-
``` rb
|
305
|
-
require 'bugsnag'
|
306
|
-
# => true
|
307
|
-
notifier = Stoplight::Notifier::Bugsnag.new(Bugsnag)
|
308
|
-
# => #<Stoplight::Notifier::Bugsnag:...>
|
309
|
-
Stoplight::Light.default_notifiers += [notifier]
|
310
|
-
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Bugsnag:...>]
|
311
|
-
```
|
325
|
+
#### IO
|
312
326
|
|
313
|
-
|
327
|
+
Stoplight can notify not only into STDOUT, but into any IO object. You can configure
|
328
|
+
the `Stoplight::Notifier::IO` notifier for that.
|
314
329
|
|
315
|
-
|
316
|
-
|
330
|
+
```ruby
|
331
|
+
require 'stringio'
|
317
332
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
Stoplight::
|
324
|
-
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Honeybadger:...>]
|
333
|
+
io = StringIO.new
|
334
|
+
# => #<StringIO:...>
|
335
|
+
notifier = Stoplight::Notifier::IO.new(io)
|
336
|
+
# => #<Stoplight::Notifier::IO:...>
|
337
|
+
Stoplight.default_notifiers += [notifier]
|
338
|
+
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::IO:...>]
|
325
339
|
```
|
326
340
|
|
327
341
|
#### Logger
|
@@ -329,77 +343,48 @@ Stoplight::Light.default_notifiers += [notifier]
|
|
329
343
|
Stoplight can be configured to use [the Logger class][] from the standard
|
330
344
|
library.
|
331
345
|
|
332
|
-
```
|
346
|
+
```ruby
|
333
347
|
require 'logger'
|
334
348
|
# => true
|
335
349
|
logger = Logger.new(STDERR)
|
336
350
|
# => #<Logger:...>
|
337
351
|
notifier = Stoplight::Notifier::Logger.new(logger)
|
338
352
|
# => #<Stoplight::Notifier::Logger:...>
|
339
|
-
Stoplight
|
353
|
+
Stoplight.default_notifiers += [notifier]
|
340
354
|
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Logger:...>]
|
341
355
|
```
|
342
356
|
|
343
|
-
####
|
357
|
+
#### Community-supported Notifiers
|
344
358
|
|
345
|
-
|
346
|
-
Stoplight.
|
359
|
+
* [stoplight-sentry]
|
347
360
|
|
348
|
-
|
349
|
-
require 'pagerduty'
|
350
|
-
# => true
|
351
|
-
pagerduty = Pagerduty.new('http://www.example.com/webhook-url')
|
352
|
-
# => #<Pagerduty:...>
|
353
|
-
notifier = Stoplight::Notifier::Pagerduty.new(pagerduty)
|
354
|
-
# => #<Stoplight::Notifier::Pagerduty:...>
|
355
|
-
Stoplight::Light.default_notifiers += [notifier]
|
356
|
-
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Pagerduty:...>]
|
357
|
-
```
|
361
|
+
You you want to implement your own notifier, the following section contains all the required information.
|
358
362
|
|
359
|
-
|
363
|
+
Pull requests to update this section are welcome.
|
360
364
|
|
361
|
-
|
362
|
-
Stoplight.
|
365
|
+
#### How to implement your own notifier?
|
363
366
|
|
364
|
-
|
365
|
-
require 'rollbar'
|
366
|
-
# => true
|
367
|
-
notifier = Stoplight::Notifier::Rollbar.new(Rollbar)
|
368
|
-
# => #<Stoplight::Notifier::Rollbar:...>
|
369
|
-
Stoplight::Light.default_notifiers += [notifier]
|
370
|
-
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Rollbar:...>]
|
371
|
-
```
|
372
|
-
|
373
|
-
#### Sentry
|
374
|
-
|
375
|
-
Make sure you have [the Sentry gem][] (`~> 1.2`) installed before configuring
|
376
|
-
Stoplight.
|
367
|
+
A notifier has to implement the `Stoplight::Notifier::Base` interface:
|
377
368
|
|
378
|
-
```
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
# => #<Raven::Configuration:...>
|
383
|
-
notifier = Stoplight::Notifier::Raven.new(configuration)
|
384
|
-
# => #<Stoplight::Notifier::Raven:...>
|
385
|
-
Stoplight::Light.default_notifiers += [notifier]
|
386
|
-
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Raven:...>]
|
369
|
+
```ruby
|
370
|
+
def notify(light, from_color, to_color, error)
|
371
|
+
raise NotImplementedError
|
372
|
+
end
|
387
373
|
```
|
388
374
|
|
389
|
-
|
375
|
+
For convenience, you can use the `Stoplight::Notifier::Generic` module. It takes care of
|
376
|
+
the message formatting, and you have to implement only the `put` method, which takes message sting as an argument:
|
390
377
|
|
391
|
-
|
392
|
-
Stoplight
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
Stoplight::Light.default_notifiers += [notifier]
|
402
|
-
# => [#<Stoplight::Notifier::IO:...>, #<Stoplight::Notifier::Slack:...>]
|
378
|
+
```ruby
|
379
|
+
class IO < Stoplight::Notifier::Base
|
380
|
+
include Generic
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
def put(message)
|
385
|
+
@object.puts(message)
|
386
|
+
end
|
387
|
+
end
|
403
388
|
```
|
404
389
|
|
405
390
|
### Rails
|
@@ -409,11 +394,11 @@ in-memory data store, you don't need to do anything special. If you want to use
|
|
409
394
|
a persistent data store, you'll need to configure it. Create an initializer for
|
410
395
|
Stoplight:
|
411
396
|
|
412
|
-
```
|
397
|
+
```ruby
|
413
398
|
# config/initializers/stoplight.rb
|
414
399
|
require 'stoplight'
|
415
|
-
Stoplight
|
416
|
-
Stoplight
|
400
|
+
Stoplight.default_data_store = Stoplight::DataStore::Redis.new(...)
|
401
|
+
Stoplight.default_notifiers += [Stoplight::Notifier::Logger.new(Rails.logger)]
|
417
402
|
```
|
418
403
|
|
419
404
|
## Advanced usage
|
@@ -421,17 +406,17 @@ Stoplight::Light.default_notifiers += [Stoplight::Notifier::Logger.new(Rails.log
|
|
421
406
|
### Locking
|
422
407
|
|
423
408
|
Although stoplights can operate on their own, occasionally you may want to
|
424
|
-
override the default behavior. You can lock a light
|
425
|
-
|
409
|
+
override the default behavior. You can lock a light using `#lock(color)` method.
|
410
|
+
Color should be either `Stoplight::Color::GREEN` or ``Stoplight::Color::RED``.
|
426
411
|
|
427
|
-
```
|
428
|
-
light = Stoplight('example-locked')
|
429
|
-
# => #<Stoplight::
|
430
|
-
light.run
|
412
|
+
```ruby
|
413
|
+
light = Stoplight('example-locked')
|
414
|
+
# => #<Stoplight::CircuitBreaker:..>
|
415
|
+
light.run { true }
|
431
416
|
# => true
|
432
|
-
light.
|
433
|
-
# =>
|
434
|
-
light.run
|
417
|
+
light.lock(Stoplight::Color::RED)
|
418
|
+
# => #<Stoplight::CircuitBreaker:..>
|
419
|
+
light.run { true }
|
435
420
|
# Stoplight::Error::RedLight: example-locked
|
436
421
|
```
|
437
422
|
|
@@ -440,10 +425,11 @@ have configured a custom data store and that data store fails, Stoplight will
|
|
440
425
|
switch over to using a blank in-memory data store. That means you will lose the
|
441
426
|
locked state of any stoplights.
|
442
427
|
|
443
|
-
You can go back to using the default behavior by unlocking the stoplight
|
428
|
+
You can go back to using the default behavior by unlocking the stoplight using `#unlock`.
|
444
429
|
|
445
|
-
```
|
446
|
-
light.
|
430
|
+
```ruby
|
431
|
+
light.unlock
|
432
|
+
# => #<Stoplight::CircuitBreaker:..>
|
447
433
|
```
|
448
434
|
|
449
435
|
### Testing
|
@@ -453,28 +439,34 @@ However there are a few things you can do to make them behave better. If your
|
|
453
439
|
stoplights are spewing messages into your test output, you can silence them
|
454
440
|
with a couple configuration changes.
|
455
441
|
|
456
|
-
```
|
457
|
-
Stoplight
|
458
|
-
Stoplight
|
442
|
+
```ruby
|
443
|
+
Stoplight.default_error_notifier = -> _ {}
|
444
|
+
Stoplight.default_notifiers = []
|
459
445
|
```
|
460
446
|
|
461
447
|
If your tests mysteriously fail because stoplights are the wrong color, you can
|
462
448
|
try resetting the data store before each test case. For example, this would
|
463
449
|
give each test case a fresh data store with RSpec.
|
464
450
|
|
465
|
-
```
|
451
|
+
```ruby
|
466
452
|
before(:each) do
|
467
|
-
Stoplight
|
453
|
+
Stoplight.default_data_store = Stoplight::DataStore::Memory.new
|
468
454
|
end
|
469
455
|
```
|
470
456
|
|
471
457
|
Sometimes you may want to test stoplights directly. You can avoid resetting the
|
472
458
|
data store by giving each stoplight a unique name.
|
473
459
|
|
474
|
-
```
|
475
|
-
stoplight = Stoplight("test-#{rand}")
|
460
|
+
```ruby
|
461
|
+
stoplight = Stoplight("test-#{rand}")
|
476
462
|
```
|
477
463
|
|
464
|
+
## Maintenance Policy
|
465
|
+
|
466
|
+
Stoplight supports the latest three minor versions of Ruby, which currently are: `3.0.x`, `3.1.x`, and `3.2.x`. Changing
|
467
|
+
the minimum supported Ruby version is not considered a breaking change.
|
468
|
+
We support the current stable Redis version (`7.2`) and the latest release of the previous major version (`6.2.9`)
|
469
|
+
|
478
470
|
## Credits
|
479
471
|
|
480
472
|
Stoplight is brought to you by [@camdez][] and [@tfausak][] from [@OrgSync][]. [@bolshakov][] is the current
|
@@ -512,3 +504,4 @@ Stoplight is licensed under [the MIT License][].
|
|
512
504
|
[complete list of contributors]: https://github.com/bolshakov/stoplight/graphs/contributors
|
513
505
|
[CircuitBreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
|
514
506
|
[the MIT license]: LICENSE.md
|
507
|
+
[stoplight-sentry]: https://github.com/bolshakov/stoplight-sentry
|