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