{"id":13846789,"url":"https://github.com/bolshakov/stoplight","last_synced_at":"2025-04-05T03:12:56.188Z","repository":{"id":19380071,"uuid":"22620860","full_name":"bolshakov/stoplight","owner":"bolshakov","description":":traffic_light: Traffic control for code.","archived":false,"fork":false,"pushed_at":"2024-04-15T06:07:05.000Z","size":609,"stargazers_count":378,"open_issues_count":6,"forks_count":40,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-04-16T03:22:33.973Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://bolshakov.github.io/stoplight/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bolshakov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2014-08-04T21:36:54.000Z","updated_at":"2024-04-22T07:47:51.513Z","dependencies_parsed_at":"2024-01-18T09:04:44.692Z","dependency_job_id":"62cdba14-3758-49a0-a81a-8b876c0e3440","html_url":"https://github.com/bolshakov/stoplight","commit_stats":null,"previous_names":["orgsync/stoplight"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolshakov%2Fstoplight","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolshakov%2Fstoplight/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolshakov%2Fstoplight/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolshakov%2Fstoplight/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bolshakov","download_url":"https://codeload.github.com/bolshakov/stoplight/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247280272,"owners_count":20912967,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T18:00:47.632Z","updated_at":"2025-04-05T03:12:56.172Z","avatar_url":"https://github.com/bolshakov.png","language":"Ruby","funding_links":[],"categories":["Ruby","Business logic"],"sub_categories":[],"readme":"# [Stoplight][]\n\n[![Version badge][]][version]\n[![Build badge][]][build]\n[![Coverage badge][]][coverage]\n[![Climate badge][]][climate]\n\nStoplight is traffic control for code. It's an implementation of the circuit\nbreaker pattern in Ruby.\n\n---\n\n:warning:️ You're currently browsing the documentation for Stoplight 4.x. If you're looking for\nthe documentation of the previous version 3.x, you can find it [here](https://github.com/bolshakov/stoplight/tree/release/v3.x).\n\nDoes your code use unreliable systems, like a flaky database or a spotty web\nservice? Wrap calls to those up in stoplights to prevent them from affecting\nthe rest of your application.\n\nCheck out [stoplight-admin][] for controlling your stoplights.\n\n- [Installation](#installation)\n- [Basic Usage](#basic-usage)\n  - [Custom Errors](#custom-errors)\n  - [Custom Fallback](#custom-fallback)\n  - [Custom Threshold](#custom-threshold)\n  - [Custom Window Size](#custom-window-size)\n  - [Custom Cool Off Time](#custom-cool-off-time)\n  - [Rails](#rails)\n- [Setup](#setup)\n  - [Data Store](#data-store)\n    - [Redis](#redis)\n  - [Notifiers](#notifiers)\n    - [IO](#io)\n    - [Logger](#logger)\n    - [Community-supported Notifiers](#community-supported-notifiers)\n    - [How to Implement Your Own Notifier?](#how-to-implement-your-own-notifier)\n  - [Rails](#rails-1)\n- [Advanced Usage](#advanced-usage)\n  - [Locking](#locking)\n  - [Testing](#testing)\n- [Maintenance Policy](#maintenance-policy)\n- [Credits](#credits)\n\n## Installation\n\nAdd it to your Gemfile:\n\n```ruby\ngem 'stoplight'\n```\n\nOr install it manually:\n\n```sh\n$ gem install stoplight\n```\n\nStoplight uses [Semantic Versioning][]. Check out [the change log][] for a\ndetailed list of changes.\n\n## Basic Usage\n\nTo get started, create a stoplight:\n\n```ruby\nlight = Stoplight('example-pi')\n```\n\nThen you can run it with a block of code and it will return the result of calling the block. This is\nthe green state. (The green state corresponds to the closed state for circuit breakers.)\n\n```ruby\nlight.run { 22.0 / 7 }\n# =\u003e 3.142857142857143\nlight.color\n# =\u003e \"green\"\n```\n\nIf everything goes well, you shouldn't even be able to tell that you're using a\nstoplight. That's not very interesting though, so let's make stoplight fail.\n\nWhen you run it, the error will be recorded and passed through. After\nrunning it a few times, the stoplight will stop trying and fail fast. This is\nthe red state. (The red state corresponds to the open state for circuit\nbreakers.)\n\n```ruby\nlight = Stoplight('example-zero')\n# =\u003e #\u003cStoplight::CircuitBreaker:...\u003e\nlight.run { 1 / 0 }\n# ZeroDivisionError: divided by 0\nlight.run { 1 / 0 }\n# ZeroDivisionError: divided by 0\nlight.run { 1 / 0 }\n# Switching example-zero from green to red because ZeroDivisionError divided by 0\n# ZeroDivisionError: divided by 0\nlight.run { 1 / 0 }\n# Stoplight::Error::RedLight: example-zero\nlight.color\n# =\u003e \"red\"\n```\n\nWhen the Stoplight changes from green to red, it will notify every configured\nnotifier. See [the notifiers section][] to learn more about notifiers.\n\nThe stoplight will move into the yellow state after being in the red state for\na while. (The yellow state corresponds to the half open state for circuit\nbreakers.) To configure how long it takes to switch into the yellow state,\ncheck out [the cool off time section][] When stoplights are yellow, they will\ntry to run their code. If it fails, they'll switch back to red. If it succeeds,\nthey'll switch to green.\n\n### Custom Errors\n\nSome errors shouldn't cause your stoplight to move into the red state. Usually\nthese are handled elsewhere in your stack and don't represent real failures. A\ngood example is `ActiveRecord::RecordNotFound`.\n\nTo prevent some errors from changing the state of your stoplight, you can\nprovide a custom block that will be called with the error and a handler\n`Proc`. It can do one of three things:\n\n1.  Re-raise the error. This causes Stoplight to ignore the error. Do this for\n    errors like `ActiveRecord::RecordNotFound` that don't represent real\n    failures.\n\n2.  Call the handler with the error. This is the default behavior. Stoplight\n    will only ignore the error if it shouldn't have been caught in the first\n    place. See `Stoplight::Error::AVOID_RESCUING` for a list of errors that\n    will be ignored.\n\n3.  Do nothing. This is **not recommended**. Doing nothing causes Stoplight to\n    never ignore the error. That means a `NoMemoryError` could change the color\n    of your stoplights.\n\n```ruby\nlight = Stoplight('example-not-found')\n  .with_error_handler do |error, handle|\n    if error.is_a?(ActiveRecord::RecordNotFound)\n      raise error\n    else      \n      handle.call(error)\n    end\n  end\n# =\u003e #\u003cStoplight::CircuitBreaker:...\u003e\nlight.run { User.find(123) }\n# ActiveRecord::RecordNotFound: Couldn't find User with ID=123\nlight.run { User.find(123) }\n# ActiveRecord::RecordNotFound: Couldn't find User with ID=123\nlight.run { User.find(123) }\n# ActiveRecord::RecordNotFound: Couldn't find User with ID=123\nlight.color\n# =\u003e \"green\"\n```\n\n### Custom Fallback\n\nBy default, stoplights will re-raise errors when they're green. When they're\nred, they'll raise a `Stoplight::Error::RedLight` error. You can provide a\nfallback that will be called in both of these cases. It will be passed the\nerror if the light was green.\n\n```ruby\nlight = Stoplight('example-fallback')\n  .with_fallback { |e| p e; 'default' }\n# =\u003e #\u003cStoplight::CircuitBreaker:..\u003e\nlight.run { 1 / 0 }\n# #\u003cZeroDivisionError: divided by 0\u003e\n# =\u003e \"default\"\nlight.run { 1 / 0 }\n# #\u003cZeroDivisionError: divided by 0\u003e\n# =\u003e \"default\"\nlight.run { 1 / 0 }\n# Switching example-fallback from green to red because ZeroDivisionError divided by 0\n# #\u003cZeroDivisionError: divided by 0\u003e\n# =\u003e \"default\"\nlight.run { 1 / 0 }\n# nil\n# =\u003e \"default\"\n```\n\n### Custom Threshold\n\nSome bits of code might be allowed to fail more or less frequently than others.\nYou can configure this by setting a custom threshold.\n\n```ruby\nlight = Stoplight('example-threshold')\n  .with_threshold(1)\n# =\u003e #\u003cStoplight::CircuitBreaker:...\u003e\nlight.run { fail }\n# Switching example-threshold from green to red because RuntimeError\n# RuntimeError:\nlight.run { fail }\n# Stoplight::Error::RedLight: example-threshold\n```\n\nThe default threshold is `3`.\n\n### Custom Window Size\n\nBy default, all recorded failures, regardless of the time these happen, will count to reach\nthe threshold (hence turning the light to red). If needed, a window size can be set,\nmeaning you can control how many errors per period of time will count to reach the red\nstate.\n\nBy default, every recorded failure contributes to reaching the threshold, regardless of when it occurs, \ncausing the stoplight to turn red. By configuring a custom window size, you control how errors are \ncounted within a specified time frame. Here's how it works:\n\nLet's say you set the window size to 2 seconds:\n\n ```ruby\nwindow_size_in_seconds = 2\n\nlight = Stoplight('example-threshold')\n  .with_window_size(window_size_in_seconds)\n  .with_threshold(1) #=\u003e #\u003cStoplight::CircuitBreaker:...\u003e\n\nlight.run { 1 / 0 } #=\u003e #\u003cZeroDivisionError: divided by 0\u003e\nsleep(3)\nlight.run { 1 / 0 }\n ```\n\nWithout the window size configuration, the second `light.run { 1 / 0 }` call will result in a\n`Stoplight::Error::RedLight` exception being raised, as the stoplight transitions to the red state \nafter the first call. With a sliding window of 2 seconds, only the errors that occur within the latest\n2 seconds are considered. The first error causes the stoplight to turn red, but after 3 seconds \n(when the second error occurs), the window has shifted, and the stoplight switches to green state \ncausing the error to raise again. This provides a way to focus on the most recent errors.\n\nThe default window size is infinity, so all failures counts.\n\n### Custom Cool Off Time\n\nStoplights will automatically attempt to recover after a certain amount of\ntime. A light in the red state for longer than the cool off period will\ntransition to the yellow state. This cool off time is customizable.\n\n```ruby\nlight = Stoplight('example-cool-off')\n  .with_cool_off_time(1)\n# =\u003e #\u003cStoplight::CircuitBreaker:...\u003e\nlight.run { fail }\n# RuntimeError:\nlight.run { fail }\n# RuntimeError:\nlight.run { fail }\n# Switching example-cool-off from green to red because RuntimeError\n# RuntimeError:\nsleep(1)\n# =\u003e 1\nlight.color\n# =\u003e \"yellow\"\nlight.run { fail }\n# RuntimeError:\n```\n\nThe default cool off time is `60` seconds. To disable automatic recovery, set\nthe cool off to `Float::INFINITY`. To make automatic recovery instantaneous,\nset the cool off to `0` seconds. Note that this is not recommended, as it\neffectively replaces the red state with yellow.\n\n### Rails\n\nStoplight was designed to wrap Rails actions with minimal effort. Here's an\nexample configuration:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  around_action :stoplight\n\n  private\n\n  def stoplight(\u0026block)\n    Stoplight(\"#{params[:controller]}##{params[:action]}\")\n      .with_fallback do |error|\n        Rails.logger.error(error)\n        render(nothing: true, status: :service_unavailable)\n      end\n      .run(\u0026block)\n  end\nend\n```\n\n## Setup\n\n### Data store\n\nStoplight uses an in-memory data store out of the box.\n\n```ruby\nrequire 'stoplight'\n# =\u003e true\nStoplight.default_data_store\n# =\u003e #\u003cStoplight::DataStore::Memory:...\u003e\n```\n\nIf you want to use a persistent data store, you'll have to set it up. Currently\nthe only supported persistent data store is Redis.\n\n#### Redis\n\nMake sure you have [the Redis gem][] installed before configuring Stoplight.\n\n```ruby\nrequire 'redis'\n# =\u003e true\nredis = Redis.new\n# =\u003e #\u003cRedis client ...\u003e\ndata_store = Stoplight::DataStore::Redis.new(redis)\n# =\u003e #\u003cStoplight::DataStore::Redis:...\u003e\nStoplight.default_data_store = data_store\n# =\u003e #\u003cStoplight::DataStore::Redis:...\u003e\n```\n\n### Notifiers\n\nStoplight sends notifications to standard error by default.\n\n``` rb\nStoplight.default_notifiers\n# =\u003e [#\u003cStoplight::Notifier::IO:...\u003e]\n```\n\nIf you want to send notifications elsewhere, you'll have to set them up.\n\n#### IO\n\nStoplight can notify not only into STDOUT, but into any IO object. You can configure \nthe `Stoplight::Notifier::IO` notifier for that.\n\n```ruby\nrequire 'stringio'\n\nio = StringIO.new\n# =\u003e #\u003cStringIO:...\u003e\nnotifier = Stoplight::Notifier::IO.new(io)\n# =\u003e #\u003cStoplight::Notifier::IO:...\u003e\nStoplight.default_notifiers += [notifier]\n# =\u003e [#\u003cStoplight::Notifier::IO:...\u003e, #\u003cStoplight::Notifier::IO:...\u003e]\n```\n\n#### Logger\n\nStoplight can be configured to use [the Logger class][] from the standard\nlibrary.\n\n```ruby\nrequire 'logger'\n# =\u003e true\nlogger = Logger.new(STDERR)\n# =\u003e #\u003cLogger:...\u003e\nnotifier = Stoplight::Notifier::Logger.new(logger)\n# =\u003e #\u003cStoplight::Notifier::Logger:...\u003e\nStoplight.default_notifiers += [notifier]\n# =\u003e [#\u003cStoplight::Notifier::IO:...\u003e, #\u003cStoplight::Notifier::Logger:...\u003e]\n```\n\n#### Community-supported Notifiers\n\n* [stoplight-sentry]\n* [stoplight-honeybadger](https://github.com/qoqa/stoplight-honeybadger)\n\nYou you want to implement your own notifier, the following section contains all the required information.\n\nPull requests to update this section are welcome.\n\n#### How to implement your own notifier?\n\nA notifier has to implement the `Stoplight::Notifier::Base` interface:\n\n```ruby\ndef notify(light, from_color, to_color, error)\n  raise NotImplementedError\nend\n```\n\nFor convenience, you can use the `Stoplight::Notifier::Generic` module. It takes care of\nthe message formatting, and you have to implement only the `put` method, which takes message sting as an argument:\n\n```ruby \nclass IO \u003c Stoplight::Notifier::Base\n  include Generic\n   \n  private\n    \n  def put(message)\n    @object.puts(message)\n  end\nend\n```\n\n### Rails\n\nStoplight is designed to work seamlessly with Rails. If you want to use the\nin-memory data store, you don't need to do anything special. If you want to use\na persistent data store, you'll need to configure it. Create an initializer for\nStoplight:\n\n```ruby\n# config/initializers/stoplight.rb\nrequire 'stoplight'\nStoplight.default_data_store = Stoplight::DataStore::Redis.new(...)\nStoplight.default_notifiers += [Stoplight::Notifier::Logger.new(Rails.logger)]\n```\n\n## Advanced usage\n\n### Locking\n\nAlthough stoplights can operate on their own, occasionally you may want to\noverride the default behavior. You can lock a light using `#lock(color)` method.\nColor should be either `Stoplight::Color::GREEN` or ``Stoplight::Color::RED``.\n\n```ruby\nlight = Stoplight('example-locked')\n# =\u003e #\u003cStoplight::CircuitBreaker:..\u003e\nlight.run { true }\n# =\u003e true\nlight.lock(Stoplight::Color::RED)\n# =\u003e #\u003cStoplight::CircuitBreaker:..\u003e\nlight.run { true } \n# Stoplight::Error::RedLight: example-locked\n```\n\n**Code in locked red lights may still run under certain conditions!** If you\nhave configured a custom data store and that data store fails, Stoplight will\nswitch over to using a blank in-memory data store. That means you will lose the\nlocked state of any stoplights.\n\nYou can go back to using the default behavior by unlocking the stoplight using `#unlock`.\n\n```ruby\nlight.unlock\n# =\u003e #\u003cStoplight::CircuitBreaker:..\u003e\n```\n\n### Testing\n\nStoplights typically work as expected without modification in test suites.\nHowever there are a few things you can do to make them behave better. If your\nstoplights are spewing messages into your test output, you can silence them\nwith a couple configuration changes.\n\n```ruby\nStoplight.default_error_notifier = -\u003e _ {}\nStoplight.default_notifiers = []\n```\n\nIf your tests mysteriously fail because stoplights are the wrong color, you can\ntry resetting the data store before each test case. For example, this would\ngive each test case a fresh data store with RSpec.\n\n```ruby\nbefore(:each) do\n  Stoplight.default_data_store = Stoplight::DataStore::Memory.new\nend\n```\n\nSometimes you may want to test stoplights directly. You can avoid resetting the\ndata store by giving each stoplight a unique name.\n\n```ruby\nstoplight = Stoplight(\"test-#{rand}\")\n```\n\n## Maintenance Policy\n\nStoplight supports the latest three minor versions of Ruby, which currently are: `3.0.x`, `3.1.x`, and `3.2.x`. Changing\nthe minimum supported Ruby version is not considered a breaking change.\nWe support the current stable Redis version (`7.2`) and the latest release of the previous major version (`6.2.9`)\n\n## Credits\n\nStoplight is brought to you by [@camdez][] and [@tfausak][] from [@OrgSync][]. [@bolshakov][] is the current \nmaintainer of the gem. A [complete list of contributors][] is available on GitHub. We were inspired by\nMartin Fowler's [CircuitBreaker][] article. \n\nStoplight is licensed under [the MIT License][].\n\n[Stoplight]: https://github.com/bolshakov/stoplight\n[Version badge]: https://img.shields.io/gem/v/stoplight.svg?label=version\n[version]: https://rubygems.org/gems/stoplight\n[Build badge]: https://github.com/bolshakov/stoplight/workflows/Specs/badge.svg\n[build]: https://github.com/bolshakov/stoplight/actions?query=branch%3Amaster\n[Coverage badge]: https://img.shields.io/coveralls/bolshakov/stoplight/master.svg?label=coverage\n[coverage]: https://coveralls.io/r/bolshakov/stoplight\n[Climate badge]: https://api.codeclimate.com/v1/badges/3451c2d281ffa345441a/maintainability\n[climate]: https://codeclimate.com/github/bolshakov/stoplight\n[stoplight-admin]: https://github.com/bolshakov/stoplight-admin\n[Semantic Versioning]: http://semver.org/spec/v2.0.0.html\n[the change log]: CHANGELOG.md\n[the notifiers section]: #notifiers\n[the cool off time section]: #custom-cool-off-time\n[the Redis gem]: https://rubygems.org/gems/redis\n[the Bugsnag gem]: https://rubygems.org/gems/bugsnag\n[the Honeybadger gem]: https://rubygems.org/gems/honeybadger\n[the Logger class]: http://ruby-doc.org/stdlib-2.2.3/libdoc/logger/rdoc/Logger.html\n[the Rollbar gem]: https://rubygems.org/gems/rollbar\n[the Sentry gem]: https://rubygems.org/gems/sentry-raven\n[the Slack gem]: https://rubygems.org/gems/slack-notifier\n[the Pagerduty gem]: https://rubygems.org/gems/pagerduty\n[@camdez]: https://github.com/camdez\n[@tfausak]: https://github.com/tfausak\n[@orgsync]: https://github.com/OrgSync\n[@bolshakov]: https://github.com/bolshakov\n[complete list of contributors]: https://github.com/bolshakov/stoplight/graphs/contributors\n[CircuitBreaker]: http://martinfowler.com/bliki/CircuitBreaker.html\n[the MIT license]: LICENSE.md\n[stoplight-sentry]: https://github.com/bolshakov/stoplight-sentry\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbolshakov%2Fstoplight","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbolshakov%2Fstoplight","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbolshakov%2Fstoplight/lists"}