stoplight 5.8.3 → 6.0.0.rc2
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/CHANGELOG.md +1 -1
- data/LICENSE.md +1 -0
- data/README.md +132 -79
- data/UPGRADING.md +265 -6
- data/lib/stoplight/admin/actions/action.rb +4 -12
- data/lib/stoplight/admin/actions/lock.rb +12 -9
- data/lib/stoplight/admin/actions/lock_all.rb +27 -0
- data/lib/stoplight/admin/actions/remove.rb +12 -9
- data/lib/stoplight/admin/actions/stats.rb +15 -12
- data/lib/stoplight/admin/actions/unlock.rb +10 -9
- data/lib/stoplight/admin/assets/apple-touch-icon.png +0 -0
- data/lib/stoplight/admin/assets/favicon.ico +0 -0
- data/lib/stoplight/admin/assets/flowbite.min.css +2 -0
- data/lib/stoplight/admin/assets/flowbite.min.js +1 -0
- data/lib/stoplight/admin/assets/icon.svg +7 -0
- data/lib/stoplight/admin/assets/turbo.es2017-esm.min.js +34 -0
- data/lib/stoplight/admin/config_registry.rb +40 -0
- data/lib/stoplight/admin/dependencies.rb +29 -28
- data/lib/stoplight/admin/helpers.rb +50 -17
- data/lib/stoplight/admin/light_view.rb +184 -0
- data/lib/stoplight/admin/lights_stats.rb +7 -9
- data/lib/stoplight/admin/views/_card.erb +19 -21
- data/lib/stoplight/admin/views/index.erb +9 -3
- data/lib/stoplight/admin/views/layout.erb +45 -13
- data/lib/stoplight/admin.rb +110 -21
- data/lib/stoplight/data_store.rb +18 -8
- data/lib/stoplight/domain/config.rb +4 -1
- data/lib/stoplight/domain/error_tracking_policy.rb +9 -0
- data/lib/stoplight/domain/failure.rb +4 -0
- data/lib/stoplight/domain/id.rb +13 -0
- data/lib/stoplight/domain/light.rb +35 -77
- data/lib/stoplight/domain/lock_control.rb +55 -0
- data/lib/stoplight/domain/matcher_validator.rb +24 -0
- data/lib/stoplight/domain/state_snapshot.rb +2 -6
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +33 -19
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +8 -2
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +56 -32
- data/lib/stoplight/domain/telemetry/bus.rb +90 -0
- data/lib/stoplight/domain/telemetry/consumer.rb +18 -0
- data/lib/stoplight/domain/telemetry/emitter.rb +45 -0
- data/lib/stoplight/domain/telemetry/envelope.rb +15 -0
- data/lib/stoplight/domain/telemetry/failure.rb +13 -0
- data/lib/stoplight/domain/telemetry/light_registered.rb +12 -0
- data/lib/stoplight/domain/telemetry/lock_changed.rb +19 -0
- data/lib/stoplight/domain/telemetry/metrics.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_failed.rb +20 -0
- data/lib/stoplight/domain/telemetry/recovery_probe_completed.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_started.rb +18 -0
- data/lib/stoplight/domain/telemetry/recovery_succeeded.rb +19 -0
- data/lib/stoplight/domain/telemetry/run_completed.rb +17 -0
- data/lib/stoplight/domain/telemetry/run_recorder.rb +58 -0
- data/lib/stoplight/domain/telemetry/settings.rb +21 -0
- data/lib/stoplight/domain/telemetry/state_transitioned.rb +11 -0
- data/lib/stoplight/domain/telemetry/subscription.rb +11 -0
- data/lib/stoplight/domain/telemetry/traffic_breached.rb +20 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +61 -17
- data/lib/stoplight/domain/tracker/request.rb +17 -8
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +13 -3
- data/lib/stoplight/domain/traffic_control/error_rate.rb +16 -12
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +9 -3
- data/lib/stoplight/error.rb +10 -1
- data/lib/stoplight/infrastructure/config_serializer.rb +50 -0
- data/lib/stoplight/infrastructure/fail_safe/error_notifier.rb +25 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/registry.rb +68 -0
- data/lib/stoplight/infrastructure/memory/storage/registry.rb +18 -0
- data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +29 -32
- data/lib/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rb +74 -0
- data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +25 -24
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +3 -8
- data/lib/stoplight/infrastructure/notifier/generic.rb +3 -3
- data/lib/stoplight/infrastructure/redis/key.rb +25 -0
- data/lib/stoplight/infrastructure/redis/storage/now.lua +9 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +2 -4
- data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +1 -1
- data/lib/stoplight/infrastructure/redis/storage/registry.rb +73 -0
- data/lib/stoplight/infrastructure/redis/storage/scripting.rb +54 -3
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +4 -2
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state.rb +8 -8
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +20 -21
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +11 -19
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +20 -16
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/_evict.lua +44 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +14 -21
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +38 -36
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +23 -30
- data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +45 -135
- data/lib/stoplight/infrastructure/system_clock.rb +4 -0
- data/lib/stoplight/telemetry.rb +19 -0
- data/lib/stoplight/types.rb +15 -2
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/config_compatibility_validator.rb +25 -2
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_config.rb +2 -1
- data/lib/stoplight/wiring/default_configuration.rb +3 -2
- data/lib/stoplight/wiring/external_caller.rb +27 -0
- data/lib/stoplight/wiring/fail_safe_config.rb +11 -0
- data/lib/stoplight/wiring/global_state.rb +93 -0
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_control_dsl.rb +3 -5
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_recovery_dsl.rb +2 -2
- data/lib/stoplight/wiring/light_configuration_dsl.rb +97 -0
- data/lib/stoplight/wiring/light_factory.rb +154 -97
- data/lib/stoplight/wiring/notifier_bridge.rb +33 -0
- data/lib/stoplight/wiring/redis/backend.rb +1 -1
- data/lib/stoplight/wiring/system/storage.rb +69 -0
- data/lib/stoplight/wiring/system.rb +125 -56
- data/lib/stoplight/wiring/{configuration_dsl.rb → system_configuration_dsl.rb} +13 -43
- data/lib/stoplight.rb +50 -194
- data/sig/sinatra/base.rbs +6 -0
- data/sig/stdlib/securerandom.rbs +4 -0
- data/sig/stoplight/admin/actions/action.rbs +10 -0
- data/sig/stoplight/admin/actions/lock.rbs +17 -0
- data/sig/stoplight/admin/actions/lock_all.rbs +17 -0
- data/sig/stoplight/admin/actions/remove.rbs +22 -0
- data/sig/stoplight/admin/actions/stats.rbs +28 -0
- data/sig/stoplight/admin/actions/unlock.rbs +17 -0
- data/sig/stoplight/admin/config_registry.rbs +20 -0
- data/sig/stoplight/admin/dependencies.rbs +16 -0
- data/sig/stoplight/admin/helpers.rbs +17 -0
- data/sig/stoplight/admin/light_view.rbs +39 -0
- data/sig/stoplight/admin/lights_stats.rbs +30 -0
- data/sig/stoplight/admin.rbs +14 -0
- data/sig/stoplight/color.rbs +3 -3
- data/sig/{_private/stoplight/data_store.rbs → stoplight/data_store/base.rbs} +1 -0
- data/sig/stoplight/data_store/memory.rbs +9 -0
- data/sig/stoplight/data_store/redis.rbs +17 -0
- data/sig/{_private/stoplight → stoplight}/domain/config.rbs +28 -24
- data/sig/{_private/stoplight → stoplight}/domain/error_tracking_policy.rbs +4 -0
- data/sig/{_private/stoplight → stoplight}/domain/failure.rbs +2 -0
- data/sig/stoplight/domain/id.rbs +7 -0
- data/sig/stoplight/domain/light.rbs +39 -0
- data/sig/{_private/stoplight → stoplight}/domain/light_info.rbs +1 -5
- data/sig/stoplight/domain/lock_control.rbs +16 -0
- data/sig/stoplight/domain/matcher_validator.rbs +11 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/clock.rbs +6 -0
- data/sig/stoplight/domain/ports/data_store.rbs +9 -0
- data/sig/stoplight/domain/ports/error_notifier.rbs +12 -0
- data/sig/stoplight/domain/ports/exception_matcher.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/metrics_store.rbs +2 -2
- data/sig/stoplight/domain/ports/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/run_strategy.rbs +2 -1
- data/sig/stoplight/domain/ports/state_transition_notifier.rbs +18 -0
- data/sig/stoplight/domain/ports/telemetry.rbs +28 -0
- data/sig/stoplight/domain/ports/telemetry_publisher.rbs +14 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_control.rbs +7 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_recovery.rbs +2 -3
- data/sig/stoplight/domain/strategies/green_run_strategy.rbs +22 -0
- data/sig/stoplight/domain/strategies/red_run_strategy.rbs +19 -0
- data/sig/{_private/stoplight → stoplight}/domain/strategies/yellow_run_strategy.rbs +23 -12
- data/sig/stoplight/domain/telemetry/bus.rbs +49 -0
- data/sig/stoplight/domain/telemetry/consumer.rbs +13 -0
- data/sig/stoplight/domain/telemetry/emitter.rbs +51 -0
- data/sig/stoplight/domain/telemetry/envelope.rbs +30 -0
- data/sig/stoplight/domain/telemetry/failure.rbs +23 -0
- data/sig/stoplight/domain/telemetry/light_registered.rbs +23 -0
- data/sig/stoplight/domain/telemetry/lock_changed.rbs +31 -0
- data/sig/stoplight/domain/telemetry/metrics.rbs +27 -0
- data/sig/stoplight/domain/telemetry/recovery_failed.rbs +36 -0
- data/sig/stoplight/domain/telemetry/recovery_probe_completed.rbs +29 -0
- data/sig/stoplight/domain/telemetry/recovery_started.rbs +26 -0
- data/sig/stoplight/domain/telemetry/recovery_succeeded.rbs +31 -0
- data/sig/stoplight/domain/telemetry/run_completed.rbs +38 -0
- data/sig/stoplight/domain/telemetry/run_recorder.rbs +27 -0
- data/sig/stoplight/domain/telemetry/settings.rbs +47 -0
- data/sig/stoplight/domain/telemetry/state_transitioned.rbs +11 -0
- data/sig/stoplight/domain/telemetry/subscription.rbs +9 -0
- data/sig/stoplight/domain/telemetry/traffic_breached.rbs +36 -0
- data/sig/stoplight/domain/telemetry/types.rbs +32 -0
- data/sig/{_private/stoplight → stoplight}/domain/tracker/recovery_probe.rbs +10 -6
- data/sig/{_private/stoplight → stoplight}/domain/tracker/request.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/consecutive_errors.rbs +2 -0
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/error_rate.rbs +3 -2
- data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery/consecutive_successes.rbs +2 -0
- data/sig/stoplight/domain/types.rbs +25 -0
- data/sig/stoplight/error.rbs +8 -0
- data/sig/stoplight/generators/rails_generators_base.rbs +11 -0
- data/sig/stoplight/infrastructure/config_serializer.rbs +12 -0
- data/sig/stoplight/infrastructure/fail_safe/error_notifier.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/metrics.rbs +2 -2
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/fail_safe/storage/registry.rbs +23 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/state.rbs +4 -4
- data/sig/stoplight/infrastructure/memory/storage/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/state.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/unbounded_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rbs +27 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/window_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/notifier/fail_safe.rbs +15 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/generic.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/io.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/logger.rbs +1 -1
- data/sig/stoplight/infrastructure/redis/key.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/redis/storage/registry.rbs +27 -0
- data/sig/stoplight/infrastructure/redis/storage/scripting.rbs +34 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/state.rbs +4 -4
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/unbounded_metrics.rbs +8 -1
- data/sig/stoplight/infrastructure/redis/storage/window_metrics.rbs +35 -0
- data/sig/stoplight/infrastructure/types.rbs +6 -0
- data/sig/stoplight/notifier.rbs +1 -1
- data/sig/stoplight/ports/system.rbs +16 -10
- data/sig/stoplight/state.rbs +3 -3
- data/sig/stoplight/telemetry.rbs +17 -0
- data/sig/stoplight/types.rbs +9 -0
- data/sig/{_private/stoplight → stoplight}/wiring/config_compatibility_validator.rbs +2 -0
- data/sig/stoplight/wiring/default.rbs +26 -0
- data/sig/stoplight/wiring/default_configuration.rbs +39 -0
- data/sig/stoplight/wiring/external_caller.rbs +13 -0
- data/sig/stoplight/wiring/fail_safe_config.rbs +7 -0
- data/sig/stoplight/wiring/global_state.rbs +36 -0
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_control_dsl.rbs +1 -1
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_recovery_dsl.rbs +1 -1
- data/sig/stoplight/wiring/light_configuration_dsl.rbs +38 -0
- data/sig/stoplight/wiring/light_factory.rbs +78 -0
- data/sig/stoplight/wiring/notifier_bridge.rbs +15 -0
- data/sig/stoplight/wiring/notifier_factory.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/wiring/redis/backend.rbs +6 -6
- data/sig/stoplight/wiring/system/storage.rbs +35 -0
- data/sig/stoplight/wiring/system.rbs +30 -0
- data/sig/stoplight/wiring/system_configuration_dsl.rbs +39 -0
- data/sig/stoplight/wiring/types.rbs +6 -0
- data/sig/stoplight.rbs +37 -54
- metadata +190 -143
- data/lib/stoplight/admin/actions/lock_all_green.rb +0 -18
- data/lib/stoplight/admin/actions/lock_green.rb +0 -23
- data/lib/stoplight/admin/actions/lock_red.rb +0 -23
- data/lib/stoplight/admin/lights_repository/light.rb +0 -171
- data/lib/stoplight/admin/lights_repository.rb +0 -87
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +0 -236
- data/lib/stoplight/infrastructure/fail_safe/data_store.rb +0 -152
- data/lib/stoplight/infrastructure/memory/data_store/metrics.rb +0 -27
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +0 -52
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +0 -17
- data/lib/stoplight/infrastructure/memory/data_store/sliding_window.rb +0 -74
- data/lib/stoplight/infrastructure/memory/data_store/state.rb +0 -21
- data/lib/stoplight/infrastructure/memory/data_store.rb +0 -342
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/get_metrics.lua +0 -26
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_failure.lua +0 -36
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +0 -27
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +0 -23
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_success.lua +0 -35
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +0 -6
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_green.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_red.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_yellow.lua +0 -9
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +0 -60
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +0 -28
- data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +0 -73
- data/lib/stoplight/infrastructure/redis/data_store.rb +0 -516
- data/lib/stoplight/infrastructure/redis/storage/key_space.rb +0 -51
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +0 -41
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +0 -33
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +0 -47
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +0 -44
- data/lib/stoplight/wiring/light_builder.rb +0 -198
- data/lib/stoplight/wiring/notifier_factory.rb +0 -26
- data/lib/stoplight/wiring/system/light_builder.rb +0 -47
- data/lib/stoplight/wiring/system/light_factory.rb +0 -64
- data/sig/_private/stoplight/domain/light.rbs +0 -25
- data/sig/_private/stoplight/domain/ports/data_store.rbs +0 -76
- data/sig/_private/stoplight/domain/ports/light_factory.rbs +0 -80
- data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/tracker/base.rbs +0 -8
- data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +0 -26
- data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +0 -25
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +0 -27
- data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +0 -30
- data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +0 -24
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +0 -21
- data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +0 -67
- data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +0 -14
- data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +0 -14
- data/sig/_private/stoplight/system/light_builder.rbs +0 -23
- data/sig/_private/stoplight/system/light_factory.rbs +0 -17
- data/sig/_private/stoplight/types.rbs +0 -6
- data/sig/_private/stoplight/wiring/configuration_dsl.rbs +0 -43
- data/sig/_private/stoplight/wiring/default.rbs +0 -26
- data/sig/_private/stoplight/wiring/default_configuration.rbs +0 -29
- data/sig/_private/stoplight/wiring/light_builder.rbs +0 -48
- data/sig/_private/stoplight/wiring/light_factory.rbs +0 -16
- data/sig/_private/stoplight/wiring/notifier_factory.rbs +0 -10
- data/sig/_private/stoplight/wiring/system.rbs +0 -15
- data/sig/_private/stoplight.rbs +0 -48
- data/sig/stoplight/data_store.rbs +0 -19
- data/sig/stoplight/ports/configuration.rbs +0 -19
- data/sig/stoplight/ports/exception_matcher.rbs +0 -8
- data/sig/stoplight/ports/light.rbs +0 -12
- data/sig/stoplight/ports/light_info.rbs +0 -5
- data/sig/stoplight/ports/state_transition_notifier.rbs +0 -15
- /data/sig/{_private/stoplight → stoplight}/common/deprecations.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/compatibility_result.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/metrics_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/state_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/state_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery.rbs +0 -0
- /data/sig/{_private → stoplight}/generators/stoplight/install/install_generator.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_lock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/system_clock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/data_store_backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/default_config.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/memory/backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set_builder.rbs +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0dc15c81e94d811df03fe4acdd4ff76e3633931a1c4df022b7c26480834135e8
|
|
4
|
+
data.tar.gz: 82b93fba4751312a3ad6ac33602b0b50c40fc6ebd5d72554d66f040ab7efd133
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a97e8dec725453ffd76e95d7d2ab59ef8929b89aa6ac2350a816d3e280aa49f893d3818dc28d6b465e20bf508c9cf10b37265e6968c436836ba9b5bdc374bb8
|
|
7
|
+
data.tar.gz: 9550e279df8c4ceaa1cc4de1a76a85dcb2fa84cb079e7dbd3716e699479b5cf94fe1d577f99f713840b693b6520d1c19b8f1b2b01fc9325b6db7cf89abdcfed3
|
data/CHANGELOG.md
CHANGED
data/LICENSE.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2015-2016 Cameron Desautels, Taylor Fausak & Justin Steffy
|
|
4
|
+
Copyright (c) 2019-2026 Tëma Bolshakov, George Asfour and contributors
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
7
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -8,8 +8,8 @@ Stoplight is a traffic control for code. It's an implementation of the circuit b
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
:warning:️ You're currently browsing the documentation for Stoplight
|
|
12
|
-
the documentation of the previous version
|
|
11
|
+
:warning:️ You're currently browsing the documentation for Stoplight 6. If you're looking for
|
|
12
|
+
the documentation of the previous version 5.x, you can find it [here](https://github.com/bolshakov/stoplight/tree/v5.8.3).
|
|
13
13
|
|
|
14
14
|
Stoplight helps your application gracefully handle failures in external dependencies
|
|
15
15
|
(like flaky databases, unreliable APIs, or spotty web services). By wrapping these unreliable
|
|
@@ -38,22 +38,7 @@ Stoplight uses [Semantic Versioning][]. Check out [the change log][] for a detai
|
|
|
38
38
|
|
|
39
39
|
Stoplight operates like a traffic light with three states:
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
stateDiagram
|
|
43
|
-
Green --> Red: Errors reach threshold
|
|
44
|
-
Red --> Yellow: After cool_off_time
|
|
45
|
-
Yellow --> Green: Successful recovery
|
|
46
|
-
Yellow --> Red: Failed recovery
|
|
47
|
-
Green --> Green: Success
|
|
48
|
-
|
|
49
|
-
classDef greenState fill:#28a745,stroke:#1e7e34,stroke-width:2px,color:#fff
|
|
50
|
-
classDef redState fill:#dc3545,stroke:#c82333,stroke-width:2px,color:#fff
|
|
51
|
-
classDef yellowState fill:#ffc107,stroke:#e0a800,stroke-width:2px,color:#000
|
|
52
|
-
|
|
53
|
-
class Green greenState
|
|
54
|
-
class Red redState
|
|
55
|
-
class Yellow yellowState
|
|
56
|
-
```
|
|
41
|
+
![Stoplight state diagram][]
|
|
57
42
|
|
|
58
43
|
- **Green**: Normal operation. Code runs as expected. (Circuit closed)
|
|
59
44
|
- **Red**: Failure state. Fast-fails without running the code. (Circuit open)
|
|
@@ -142,53 +127,51 @@ receives `nil`. In both cases, the return value of the fallback becomes the retu
|
|
|
142
127
|
|
|
143
128
|
## Admin Panel
|
|
144
129
|
|
|
145
|
-
Stoplight comes with a built-in Admin Panel
|
|
130
|
+
Stoplight comes with a built-in Admin Panel for observing and controlling all lights across your application. It
|
|
131
|
+
displays each light's current state, recent failures, and provides controls to lock/unlock lights manually.
|
|
146
132
|
|
|
147
133
|

|
|
148
134
|
|
|
149
|
-
|
|
135
|
+
### Basic Setup
|
|
136
|
+
|
|
137
|
+
Add the Admin Panel to your Rails application with authentication:
|
|
150
138
|
|
|
151
139
|
```ruby
|
|
152
140
|
Rails.application.routes.draw do
|
|
153
|
-
# ...
|
|
154
|
-
|
|
155
141
|
Stoplight::Admin.use(Rack::Auth::Basic) do |username, password|
|
|
156
142
|
username == ENV["STOPLIGHT_ADMIN_USERNAME"] && password == ENV["STOPLIGHT_ADMIN_PASSWORD"]
|
|
157
143
|
end
|
|
158
144
|
mount Stoplight::Admin => '/stoplights'
|
|
159
|
-
|
|
160
|
-
# ...
|
|
161
145
|
end
|
|
162
146
|
```
|
|
163
147
|
|
|
164
|
-
Then set
|
|
148
|
+
Then set environment variables:
|
|
149
|
+
```bash
|
|
150
|
+
export STOPLIGHT_ADMIN_USERNAME=admin
|
|
151
|
+
export STOPLIGHT_ADMIN_PASSWORD=secret
|
|
152
|
+
```
|
|
165
153
|
|
|
166
|
-
**IMPORTANT:** Stoplight Admin Panel requires
|
|
154
|
+
**IMPORTANT:** Stoplight Admin Panel requires `sinatra` and `sinatra-contrib` gems:
|
|
167
155
|
|
|
168
156
|
```ruby
|
|
169
157
|
gem "sinatra", require: false
|
|
170
158
|
gem "sinatra-contrib", require: false
|
|
171
159
|
```
|
|
172
160
|
|
|
173
|
-
|
|
174
|
-
```ruby
|
|
175
|
-
gem install sinatra
|
|
176
|
-
gem install sinatra-contrib
|
|
177
|
-
```
|
|
161
|
+
### Standalone Docker Setup
|
|
178
162
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
It is possible to run the Admin Panel separately from your application using the `stoplight-admin:<release-version>` docker image.
|
|
163
|
+
Run the Admin Panel as a separate service:
|
|
182
164
|
|
|
183
165
|
```shell
|
|
184
|
-
docker run
|
|
166
|
+
docker run \
|
|
167
|
+
-e REDIS_URL=redis://localhost:6379 \
|
|
168
|
+
-e STOPLIGHT_ADMIN_USERNAME=admin \
|
|
169
|
+
-e STOPLIGHT_ADMIN_PASSWORD=secret \
|
|
170
|
+
-p 4567:4567 \
|
|
171
|
+
bolshakov/stoplight-admin
|
|
185
172
|
```
|
|
186
173
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
```shell
|
|
190
|
-
docker run -e REDIS_URL=redis://localhost:6378 --net=host bolshakov/stoplight-admin
|
|
191
|
-
```
|
|
174
|
+
For complete setup and multi-system configuration details, see the [Admin Panel guide](docs/admin.md).
|
|
192
175
|
|
|
193
176
|
## Configuration
|
|
194
177
|
|
|
@@ -226,36 +209,16 @@ light = Stoplight("Payment Service")
|
|
|
226
209
|
You can also provide settings during creation:
|
|
227
210
|
|
|
228
211
|
```ruby
|
|
229
|
-
data_store = Stoplight::DataStore::Redis.new(Redis.new)
|
|
230
|
-
|
|
231
212
|
light = Stoplight("Payment Service",
|
|
232
213
|
window_size: 300, # Only count errors in the last five minutes
|
|
233
214
|
threshold: 5, # 5 errors before turning red
|
|
234
215
|
cool_off_time: 60, # Wait 60 seconds before attempting recovery
|
|
235
216
|
recovery_threshold: 1, # 1 successful attempt to turn green again
|
|
236
|
-
data_store: data_store, # Use Redis for persistence
|
|
237
217
|
tracked_errors: [TimeoutError], # Only count TimeoutError
|
|
238
218
|
skipped_errors: [ValidationError] # Ignore ValidationError
|
|
239
219
|
)
|
|
240
220
|
```
|
|
241
221
|
|
|
242
|
-
### Modifying Stoplights
|
|
243
|
-
|
|
244
|
-
You can create specialized versions of existing stoplights:
|
|
245
|
-
|
|
246
|
-
```ruby
|
|
247
|
-
# Base configuration for API calls
|
|
248
|
-
base_api = Stoplight("Service API")
|
|
249
|
-
|
|
250
|
-
# Create specialized version for the users endpoint
|
|
251
|
-
users_api = base_api.with(
|
|
252
|
-
tracked_errors: [TimeoutError] # Only track timeouts
|
|
253
|
-
)
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
The `#with` method creates a new stoplight instance without modifying the original, making it ideal for creating
|
|
257
|
-
specialized stoplights from a common configuration.
|
|
258
|
-
|
|
259
222
|
## Error Handling
|
|
260
223
|
|
|
261
224
|
By default, Stoplight tracks all `StandardError` exceptions.
|
|
@@ -277,8 +240,41 @@ light = Stoplight("Example API", tracked_errors: [NetworkError, Timeout::Error])
|
|
|
277
240
|
|
|
278
241
|
When both methods are used, `skipped_errors` takes precedence over `tracked_errors`.
|
|
279
242
|
|
|
243
|
+
Either list can be replaced for a single call without changing the light's configuration:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
light.run(tracked_errors: [Timeout::Error]) { fetch_data }
|
|
247
|
+
light.run(skipped_errors: [ValidationError]) { process_data }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Any list omitted from `run` keeps its configured value. The provided list is replaced only for that call, and
|
|
251
|
+
`skipped_errors` still takes precedence over `tracked_errors`.
|
|
252
|
+
|
|
280
253
|
## Advanced Configuration
|
|
281
254
|
|
|
255
|
+
### Registering Lights
|
|
256
|
+
|
|
257
|
+
Calling `Stoplight("name", ...)` at every call site works well for a handful of lights. As an app
|
|
258
|
+
grows, repeating the same settings everywhere makes them easy to drift out of sync, and there's no
|
|
259
|
+
single place listing what lights exist.
|
|
260
|
+
|
|
261
|
+
Register a light once and look it up by name wherever you need it, instead of repeating the same
|
|
262
|
+
settings at every call site.
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# config/initializers/stoplight.rb
|
|
266
|
+
Stoplight.register("Payment Service", threshold: 5, cool_off_time: 60)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# anywhere else in your app
|
|
271
|
+
Stoplight.light("Payment Service").run { payment_gateway.process(order) }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`Stoplight("name", ...)` still works as shown above -- registration is an addition, not a replacement.
|
|
275
|
+
`Stoplight.light` is also approximately 10 times faster, since it's a plain lookup rather than re-validating
|
|
276
|
+
the configuration on every call.
|
|
277
|
+
|
|
282
278
|
### Traffic Control Strategies
|
|
283
279
|
|
|
284
280
|
You've seen how Stoplight transitions from green to red when errors reach the threshold. But **how exactly does it
|
|
@@ -340,18 +336,9 @@ light = Stoplight(
|
|
|
340
336
|
|
|
341
337
|
Monitors error rate over a 5-minute sliding window. The stoplight turns red when error rate exceeds 50%.
|
|
342
338
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
traffic_control: {
|
|
347
|
-
error_rate: { min_requests: 20 },
|
|
348
|
-
},
|
|
349
|
-
window_size: 300,
|
|
350
|
-
threshold: 0.5,
|
|
351
|
-
)
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
Only evaluates error rate after at least 20 requests within the window. Default `min_requests` is 10.
|
|
339
|
+
Error rate evaluation starts only after 100 requests within the window — enough samples
|
|
340
|
+
for a statistically reliable estimate. If your service handles fewer than 100 requests
|
|
341
|
+
per window, the breaker will never trip on error rate; use `traffic_control: :consecutive_errors` instead.
|
|
355
342
|
|
|
356
343
|
|
|
357
344
|
#### When to use:
|
|
@@ -488,6 +475,24 @@ By default, Stoplight logs state transitions to STDERR.
|
|
|
488
475
|
Pull requests to update this section are welcome. If you want to implement your own notifier, refer to
|
|
489
476
|
the [notifier interface documentation] for detailed instructions. Pull requests to update this section are welcome.
|
|
490
477
|
|
|
478
|
+
### Telemetry
|
|
479
|
+
|
|
480
|
+
Notifiers only fire on state transitions. For everything else a light does - every run, trip, recovery probe, and
|
|
481
|
+
manual lock - subscribe to the telemetry bus:
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
Stoplight.telemetry.subscribe(Stoplight::Telemetry::TrafficBreached) do |envelope|
|
|
485
|
+
logger.warn("#{envelope.light_name} tripped: #{envelope.payload.failure&.exception&.message}")
|
|
486
|
+
end
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
The [stoplight-statsd] gem is built on this bus. It forwards every event to Statsd, so a dashboard of your circuit
|
|
490
|
+
breakers is a `bundle add` away:
|
|
491
|
+
|
|
492
|
+

|
|
493
|
+
|
|
494
|
+
See the [Telemetry guide](docs/telemetry.md) for the full event list and the envelope format.
|
|
495
|
+
|
|
491
496
|
### Error Notifiers
|
|
492
497
|
|
|
493
498
|
Stoplight is built for resilience. If the Redis data store fails, Stoplight automatically falls back to the in-memory
|
|
@@ -522,6 +527,52 @@ light.lock(Stoplight::Color::GREEN)
|
|
|
522
527
|
light.unlock
|
|
523
528
|
```
|
|
524
529
|
|
|
530
|
+
### Multiple Independent Systems
|
|
531
|
+
|
|
532
|
+
By default, all lights share the same global configuration and data store. For larger applications with multiple
|
|
533
|
+
services or tenants, you can create **named systems** -- completely isolated instances with their own configuration,
|
|
534
|
+
notifiers, and data store:
|
|
535
|
+
|
|
536
|
+
```ruby
|
|
537
|
+
# Create independent systems with separate data stores
|
|
538
|
+
Payments = Stoplight.register_system("Payments", threshold: 3, cool_off_time: 30)
|
|
539
|
+
Analytics = Stoplight.register_system("Analytics", threshold: 5, cool_off_time: 60)
|
|
540
|
+
|
|
541
|
+
# Register lights in each system
|
|
542
|
+
Payments.register("stripe", cool_off_time: 30)
|
|
543
|
+
Analytics.register("amplitude")
|
|
544
|
+
|
|
545
|
+
# Use them — one system's state does not affect another
|
|
546
|
+
Payments.light("stripe").run { charge_card }
|
|
547
|
+
Analytics.light("amplitude").run { track_event }
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Use cases for multiple systems:
|
|
551
|
+
|
|
552
|
+
* **Multi-tenancy**: Each tenant gets its own isolated system and data store
|
|
553
|
+
* **Service boundaries**: Separate failure domains with independent SLOs (e.g., payments vs. analytics)
|
|
554
|
+
* **Independent data stores**: One service uses Redis for persistence, another uses in-memory
|
|
555
|
+
|
|
556
|
+
For complete details on system configuration, boot-time registration patterns, and isolation guarantees, see
|
|
557
|
+
the [Systems guide](docs/systems.md).
|
|
558
|
+
|
|
559
|
+
### Admin
|
|
560
|
+
|
|
561
|
+
Admin Panel can work in an read-only which could be useful for observability. To enabled read-only mode:
|
|
562
|
+
|
|
563
|
+
```ruby
|
|
564
|
+
Stoplight::Admin.configure do |config|
|
|
565
|
+
config.read_only = true
|
|
566
|
+
end
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Read-only mode could be turned on for a pre-built docker image by passing `STOPLIGHT_ADMIN_READ_ONLY` environment
|
|
570
|
+
variable:
|
|
571
|
+
|
|
572
|
+
```sh
|
|
573
|
+
docker run -e REDIS_URL=redis://localhost:6378 -e STOPLIGHT_ADMIN_READ_ONLY=true --net=host bolshakov/stoplight-admin
|
|
574
|
+
```
|
|
575
|
+
|
|
525
576
|
## Rails Integration
|
|
526
577
|
|
|
527
578
|
Wrap controller actions with minimal effort:
|
|
@@ -602,13 +653,13 @@ We only actively support the latest major version of Stoplight.
|
|
|
602
653
|
|
|
603
654
|
**Ruby**: Major versions that receive security updates (see [Ruby Maintenance Branches]):
|
|
604
655
|
|
|
605
|
-
* Currently: Ruby 3.
|
|
656
|
+
* Currently: Ruby 3.3.x, 3.4.x and 4.0.x
|
|
606
657
|
* We test against these versions in CI
|
|
607
658
|
|
|
608
659
|
**Data Stores**: Current supported versions from upstream (versions that receive security updates):
|
|
609
660
|
|
|
610
|
-
* Redis: 8.
|
|
611
|
-
* Valkey: 8.0.x, 7.2.x (following [Valkey's support policy])
|
|
661
|
+
* Redis: 8.6.x, 8.4.x, 7.4.x (following [Redis's support policy])
|
|
662
|
+
* Valkey: 9.1.x, 9.0.x, 8.1.x, 8.0.x, 7.2.x (following [Valkey's support policy])
|
|
612
663
|
* We test against the latest version of each major release
|
|
613
664
|
|
|
614
665
|
For dependencies:
|
|
@@ -622,7 +673,7 @@ For dependencies:
|
|
|
622
673
|
* Ruby: When Ruby core team ends security support, we drop it in our next major release
|
|
623
674
|
* Data Stores: When Redis/Valkey ends maintenance, we drop it in our next major release
|
|
624
675
|
|
|
625
|
-
Example: "Ruby 3.
|
|
676
|
+
Example: "Ruby 3.3 reaches end-of-life in March 2027, so Stoplight 7.0 will require Ruby 3.4+"
|
|
626
677
|
|
|
627
678
|
## Development
|
|
628
679
|
|
|
@@ -639,15 +690,16 @@ Fowler’s [CircuitBreaker][] article.
|
|
|
639
690
|
[Stoplight]: https://github.com/bolshakov/stoplight
|
|
640
691
|
[Version badge]: https://img.shields.io/gem/v/stoplight.svg?label=version
|
|
641
692
|
[version]: https://rubygems.org/gems/stoplight
|
|
642
|
-
[Build badge]: https://github.com/bolshakov/stoplight/workflows/
|
|
643
|
-
[build]: https://github.com/bolshakov/stoplight/actions?query=branch%
|
|
644
|
-
[Coverage badge]: https://img.shields.io/coveralls/bolshakov/stoplight/
|
|
693
|
+
[Build badge]: https://github.com/bolshakov/stoplight/actions/workflows/ci.yml/badge.svg?branch=main
|
|
694
|
+
[build]: https://github.com/bolshakov/stoplight/actions?query=branch%3Amain
|
|
695
|
+
[Coverage badge]: https://img.shields.io/coveralls/bolshakov/stoplight/main.svg?label=coverage
|
|
645
696
|
[coverage]: https://coveralls.io/r/bolshakov/stoplight
|
|
646
697
|
[stoplight-admin]: https://github.com/bolshakov/stoplight-admin
|
|
647
698
|
[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
|
|
648
699
|
[the change log]: CHANGELOG.md
|
|
649
700
|
[stoplight-sentry]: https://github.com/bolshakov/stoplight-sentry
|
|
650
701
|
[stoplight-honeybadger]: https://github.com/qoqa/stoplight-honeybadger
|
|
702
|
+
[stoplight-statsd]: https://github.com/bolshakov/stoplight-statsd
|
|
651
703
|
[notifier interface documentation]: https://github.com/bolshakov/stoplight/blob/main/lib/stoplight/domain/state_transition_notifier.rb
|
|
652
704
|
[camdez]: https://github.com/camdez
|
|
653
705
|
[tfausak]: https://github.com/tfausak
|
|
@@ -663,3 +715,4 @@ Fowler’s [CircuitBreaker][] article.
|
|
|
663
715
|
[Valkey's support policy]: https://valkey.io/topics/releases/
|
|
664
716
|
[DragonflyDB]: https://www.dragonflydb.io/
|
|
665
717
|
[DragonflyDB documentation]: https://www.dragonflydb.io/docs/managing-dragonfly/scripting#script-flags
|
|
718
|
+
[Stoplight state diagram]: assets/state-diagram.svg
|
data/UPGRADING.md
CHANGED
|
@@ -1,3 +1,261 @@
|
|
|
1
|
+
## Stoplight 6.0
|
|
2
|
+
|
|
3
|
+
Stoplight 6.0 is built around a single idea: a light is a named thing your application declares once, not an object
|
|
4
|
+
you rebuild at every call site. Most of the changes below follow from that. The first two need some thought, the rest
|
|
5
|
+
are mechanical.
|
|
6
|
+
|
|
7
|
+
Here's what you'll want to tackle during your upgrade, roughly ordered from the most involved to the most trivial:
|
|
8
|
+
|
|
9
|
+
- [ ] Make every `Stoplight("name", ...)` call site for the same name pass identical settings
|
|
10
|
+
- [ ] Move `data_store`, `notifiers`, and `error_notifier` from individual lights to `Stoplight.configure`
|
|
11
|
+
- [ ] Replace the `with_*` builder methods with settings passed at creation time
|
|
12
|
+
- [ ] Replace proc and anonymous-class error matchers with named classes or modules
|
|
13
|
+
- [ ] Account for Stoplight state reset after deployment
|
|
14
|
+
- [ ] Re-check `error_rate` lights - `min_requests` is gone and the fixed minimum sample is now 100 requests
|
|
15
|
+
- [ ] Round any fractional `window_size` up to a whole number of seconds
|
|
16
|
+
- [ ] Round any fractional `cool_off_time` up to a whole number of seconds, at least 1
|
|
17
|
+
- [ ] Drop `warn_on_clock_skew` from your Redis data store setup
|
|
18
|
+
- [ ] Test thoroughly in a staging environment
|
|
19
|
+
|
|
20
|
+
### Lights Are Registered and Reused
|
|
21
|
+
|
|
22
|
+
In 5.x, each `Stoplight("Payment Service", threshold: 5)` call built a brand-new light. Nothing tied the call sites
|
|
23
|
+
together, so two places could configure the same name differently and both would run - which settings a process ended
|
|
24
|
+
up with depended on which file it executed.
|
|
25
|
+
|
|
26
|
+
In 6.0, `Stoplight()` registers the light on first call and returns that same cached instance afterwards. If a later
|
|
27
|
+
call passes settings that differ from the registration, Stoplight raises `Stoplight::Error::ConfigurationError`. The
|
|
28
|
+
message names where the light was first registered, and the error's backtrace points at the conflicting call:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Light `Payment Service` already registered with different configuration.
|
|
32
|
+
|
|
33
|
+
Originally registered at:
|
|
34
|
+
app/services/payment_service.rb:14:in 'charge'
|
|
35
|
+
|
|
36
|
+
Lights must have consistent configuration across all call sites.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Your existing code keeps working as long as each name is configured consistently, so for many applications this
|
|
40
|
+
upgrade is a no-op until a stray call site raises. The more durable fix is to register lights once at boot and look
|
|
41
|
+
them up by name:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# config/initializers/stoplight.rb
|
|
45
|
+
Stoplight.register("Payment Service", threshold: 5, cool_off_time: 60)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# anywhere else in your app
|
|
50
|
+
Stoplight.light("Payment Service").run { payment_gateway.charge(order) }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`Stoplight()` still works everywhere it used to - it just expects the same settings each time. `Stoplight.light` is
|
|
54
|
+
the alternative: it takes a name and nothing else, so there's no settings list to keep in sync, and it raises
|
|
55
|
+
`Stoplight::Error::UnregisteredLightError` if that name was never registered.
|
|
56
|
+
|
|
57
|
+
Lights built from dynamic names (`Stoplight("api-#{endpoint}")`) still work - caching is per name, so each distinct
|
|
58
|
+
name registers once.
|
|
59
|
+
|
|
60
|
+
### Infrastructure Configuration Moved Out of Individual Lights
|
|
61
|
+
|
|
62
|
+
`Stoplight()` and `Stoplight.light()` no longer accept `data_store`, `notifiers`, or `error_notifier`. Passing them
|
|
63
|
+
now raises `ArgumentError: unknown keyword`.
|
|
64
|
+
|
|
65
|
+
These options aren't per-circuit behavior - they describe where your application keeps state and how it reports
|
|
66
|
+
problems. When each light could carry its own, the same light name could end up backed by different stores in
|
|
67
|
+
different processes, so instances never saw each other's failures. It also left no single place listing which lights
|
|
68
|
+
exist and where their state lives, which the Admin dashboard's registry needs.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Old way that won't work anymore
|
|
72
|
+
light = Stoplight("Payment Service", data_store: Stoplight::DataStore::Redis.new(redis))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Configure infrastructure once, at boot:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# New way
|
|
79
|
+
Stoplight.configure do |config|
|
|
80
|
+
config.data_store = Stoplight::DataStore::Redis.new(redis)
|
|
81
|
+
config.notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)]
|
|
82
|
+
config.error_notifier = ->(error) { Bugsnag.notify(error) }
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If you genuinely need more than one data store - separate Redis instances per tenant, for example - use named
|
|
87
|
+
systems, which own their own infrastructure. See [Systems: Namespace Isolation & Configuration][systems] for setup.
|
|
88
|
+
|
|
89
|
+
### The Light Builder API Is Gone
|
|
90
|
+
|
|
91
|
+
`Light#with` and the whole `with_*` family are removed: `with_data_store`, `with_cool_off_time`, `with_threshold`,
|
|
92
|
+
`with_window_size`, `with_notifiers`, `with_error_notifier`, `with_tracked_errors`, and `with_skipped_errors`. They
|
|
93
|
+
were marked deprecated in 5.0 and have printed a warning naming this release since 5.7, so most codebases should
|
|
94
|
+
already be clean.
|
|
95
|
+
|
|
96
|
+
Cloning a light produced a second light sharing the first one's name but not its settings - exactly the configuration
|
|
97
|
+
drift the registration model now rejects.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
# Old way
|
|
101
|
+
light = Stoplight("Payment Service").with_threshold(10).with_cool_off_time(30)
|
|
102
|
+
|
|
103
|
+
# New way
|
|
104
|
+
light = Stoplight("Payment Service", threshold: 10, cool_off_time: 30)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If you were cloning in order to vary error handling per call, you don't need a second light for that anymore -
|
|
108
|
+
`#run` takes per-call overrides:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
light.run(skipped_errors: [ActiveRecord::RecordNotFound]) { find_customer }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Error Matchers Must Be Named Classes or Modules
|
|
115
|
+
|
|
116
|
+
`tracked_errors` and `skipped_errors` accept named `Class` or `Module` constants only - `StandardError`, your own
|
|
117
|
+
error classes, or a custom class or module overriding `===`. Procs, anonymous classes (`Class.new(StandardError)`),
|
|
118
|
+
and instances raise `ArgumentError`. In 5.x anything responding to `===` was accepted.
|
|
119
|
+
|
|
120
|
+
Both the registry and the consistency check need a matcher's *name*. The registry writes each light's configuration
|
|
121
|
+
to Redis so the Admin dashboard can list lights it never loaded, and a matcher is stored there as its class name - a
|
|
122
|
+
proc has no name to write. Comparing call sites has the same problem from the other direction: two files that each
|
|
123
|
+
build their own `->(error) { ... }` produce two different objects, so identical-looking configuration would be
|
|
124
|
+
reported as a conflict. A constant is the same matcher everywhere it's mentioned.
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Old way that won't work anymore
|
|
128
|
+
light = Stoplight("Payment Service", skipped_errors: [->(error) { error.is_a?(Timeout::Error) }])
|
|
129
|
+
|
|
130
|
+
# New way
|
|
131
|
+
light = Stoplight("Payment Service", skipped_errors: [Timeout::Error])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For matching that genuinely needs logic, give it a name and let it override `===`:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
module TransientHTTPError
|
|
138
|
+
def self.===(error) = error.is_a?(Net::HTTPError) && error.response.code.start_with?("5")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
light = Stoplight("Payment Service", tracked_errors: [TransientHTTPError])
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The restriction applies to registration only. The same two lists can be passed to `#run` as a per-call override, and
|
|
145
|
+
those are never registered, serialized, or compared against another call site - so anything responding to `===` still
|
|
146
|
+
works there, procs included:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
light.run(skipped_errors: ->(error) { error.message.include?("rate limit") }) { charge_card }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Redis Data Gets a Fresh Start Again
|
|
153
|
+
|
|
154
|
+
Like 5.0 before it, 6.0 moves to a new Redis key schema. Keys are now namespaced `stoplight:v6:...` instead of
|
|
155
|
+
`stoplight:v5:...`, and the storage behind them was decomposed into focused stores for state, metrics, and recovery
|
|
156
|
+
locks.
|
|
157
|
+
|
|
158
|
+
The reason is the same one that motivated the split: a single monolithic key structure made every read pull data no
|
|
159
|
+
caller needed, and it left no room for the per-light configuration the registry and dashboard now persist. Migrating
|
|
160
|
+
the old format would have meant a rewrite of live data for a value that decays within minutes anyway.
|
|
161
|
+
|
|
162
|
+
Nothing to change in your code. After deploying, every circuit breaker starts green with no history. For most
|
|
163
|
+
applications that's a non-event, since circuit breakers are meant to react to current conditions - but if you're
|
|
164
|
+
deploying while a dependency is already failing, expect the first failures after the deploy to be passed through
|
|
165
|
+
before the light trips again.
|
|
166
|
+
|
|
167
|
+
Your old `stoplight:v5:*` keys are ignored from the moment you deploy, and they carry TTLs, so they expire on their
|
|
168
|
+
own. There's no cleanup to do.
|
|
169
|
+
|
|
170
|
+
### Error Rate No Longer Takes `min_requests`
|
|
171
|
+
|
|
172
|
+
The `error_rate` traffic control strategy dropped its `min_requests` option. The minimum sample size is now fixed at
|
|
173
|
+
100 requests internally, up from a default of 10.
|
|
174
|
+
|
|
175
|
+
This changes behavior even if you never passed the option: an `error_rate` light now needs 100 requests in its window
|
|
176
|
+
before it can trip at all.
|
|
177
|
+
|
|
178
|
+
An error rate is only a useful signal when it's measured over enough requests. Across 10 requests - the old default -
|
|
179
|
+
a single failure moves the rate by 10 percentage points, so the light reacted to normal variation as readily as to a
|
|
180
|
+
real problem. At 100 requests each individual request moves the measurement by one point, which is stable enough to
|
|
181
|
+
act on. Since the value that makes the strategy trustworthy is the one nobody should tune down, it stopped being a
|
|
182
|
+
knob.
|
|
183
|
+
|
|
184
|
+
The practical consequence is that `error_rate` is a strategy for lights that see real traffic. If a light won't see
|
|
185
|
+
100 requests within its `window_size`, use `:consecutive_errors` instead.
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
# Old way that won't work anymore
|
|
189
|
+
light = Stoplight("Payment Service", traffic_control: {error_rate: {min_requests: 10}}, threshold: 0.5)
|
|
190
|
+
|
|
191
|
+
# New way
|
|
192
|
+
light = Stoplight("Payment Service", traffic_control: :error_rate, threshold: 0.5, window_size: 300)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The hash form of `traffic_control` is gone entirely, so passing it raises `Stoplight::Error::ConfigurationError`.
|
|
196
|
+
`Stoplight::Domain::TrafficControl::ErrorRate.new` takes no arguments.
|
|
197
|
+
|
|
198
|
+
### `window_size` Must Be a Whole Number of Seconds
|
|
199
|
+
|
|
200
|
+
`window_size` now accepts only an `Integer` of at least 1. A `Float`, or anything below one second, raises
|
|
201
|
+
`Stoplight::Error::ConfigurationError` when the value is applied - at `Stoplight()`, `Stoplight.register_system`, or
|
|
202
|
+
`Stoplight.configure`. `nil` still means "no window".
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
# Old way that won't work anymore
|
|
206
|
+
light = Stoplight("Payment Service", window_size: 59.5, traffic_control: :error_rate, threshold: 0.5)
|
|
207
|
+
|
|
208
|
+
# New way
|
|
209
|
+
light = Stoplight("Payment Service", window_size: 60, traffic_control: :error_rate, threshold: 0.5)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Metrics are counted in per-second buckets, so a second is the smallest span the window can actually measure. A
|
|
213
|
+
fractional value was never honoured as written - both stores truncated it to whole buckets, so `window_size: 59.5`
|
|
214
|
+
measured 59 seconds on Redis and 59 or 60 in memory depending on when you asked, and anything under a second evicted
|
|
215
|
+
the current bucket on most calls, so failures rarely accumulated enough to trip the light. None of that has changed -
|
|
216
|
+
the window is measured exactly as it always was. What changes is that the mismatch is no longer hidden: instead of
|
|
217
|
+
quietly measuring a different span than you asked for, Stoplight escalates it as an error the moment the light is
|
|
218
|
+
configured.
|
|
219
|
+
|
|
220
|
+
### `cool_off_time` Must Be a Whole Number of Seconds
|
|
221
|
+
|
|
222
|
+
`cool_off_time` now accepts only an `Integer` of at least 1. A `Float`, or anything below one second, raises
|
|
223
|
+
`Stoplight::Error::ConfigurationError` when the value is applied - at `Stoplight()`, `Stoplight.register_system`, or
|
|
224
|
+
`Stoplight.configure`.
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
# Old way that won't work anymore
|
|
228
|
+
light = Stoplight("Payment Service", cool_off_time: 1.5)
|
|
229
|
+
|
|
230
|
+
# New way
|
|
231
|
+
light = Stoplight("Payment Service", cool_off_time: 2)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Below a second the light turns yellow again before the failing dependency could plausibly have recovered, so it
|
|
235
|
+
probes on nearly every call instead of breaking the circuit. Above that, a fraction of a second is marginal against
|
|
236
|
+
the window the light measures failures over, so whole seconds are the only granularity worth expressing.
|
|
237
|
+
|
|
238
|
+
### Clock Skew Detection Is Gone
|
|
239
|
+
|
|
240
|
+
`Stoplight::DataStore::Redis.new` no longer accepts `warn_on_clock_skew:`. Passing it raises `ArgumentError`.
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
# Old way that won't work anymore
|
|
244
|
+
Stoplight::DataStore::Redis.new(redis, warn_on_clock_skew: false)
|
|
245
|
+
|
|
246
|
+
# New way
|
|
247
|
+
Stoplight::DataStore::Redis.new(redis)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
All time-dependent decisions are now made from Redis's own clock, so every instance reads the same time no matter
|
|
251
|
+
what its host believes. With the coordination problem solved at the source, there is no skew left to warn about.
|
|
252
|
+
|
|
253
|
+
### Getting Help
|
|
254
|
+
|
|
255
|
+
If you run into anything this guide doesn't cover, post to our [Discussions forum]. The configuration errors in 6.0
|
|
256
|
+
try hard to tell you exactly which two call sites disagree and what to do about it, so start by reading the message
|
|
257
|
+
in full - it usually names the file and line you need.
|
|
258
|
+
|
|
1
259
|
## Stoplight 5.0
|
|
2
260
|
|
|
3
261
|
Stoplight 5.0 introduces several breaking changes, so you'll need to set aside some time to update your code. The good
|
|
@@ -7,12 +265,12 @@ more powerful setup.
|
|
|
7
265
|
Here's what you'll want to tackle during your upgrade. Don't worry if this looks like a lot - most of these are simple
|
|
8
266
|
find-and-replace operations:
|
|
9
267
|
|
|
10
|
-
- [] Update global configuration to use the new block syntax
|
|
11
|
-
- [] Replace any remaining `Stoplight() {}` calls with `Stoplight().run {}`
|
|
12
|
-
- [] Convert error handlers to tracked/skipped error lists
|
|
13
|
-
- [] Move fallbacks from configuration to `#run` method calls
|
|
14
|
-
- [] Account for Stoplight state reset after deployment
|
|
15
|
-
- [] Test thoroughly in a staging environment
|
|
268
|
+
- [ ] Update global configuration to use the new block syntax
|
|
269
|
+
- [ ] Replace any remaining `Stoplight() {}` calls with `Stoplight().run {}`
|
|
270
|
+
- [ ] Convert error handlers to tracked/skipped error lists
|
|
271
|
+
- [ ] Move fallbacks from configuration to `#run` method calls
|
|
272
|
+
- [ ] Account for Stoplight state reset after deployment
|
|
273
|
+
- [ ] Test thoroughly in a staging environment
|
|
16
274
|
|
|
17
275
|
### Global Configuration Redesign
|
|
18
276
|
|
|
@@ -300,4 +558,5 @@ Nothing. Stoplight will function as usual.
|
|
|
300
558
|
[dropped notifiers]: https://github.com/bolshakov/stoplight/tree/v3.0.1/lib/stoplight/notifier
|
|
301
559
|
[without passing an empty block]: https://github.com/bolshakov/stoplight-admin/blob/9c9848eb94410e46b20972548f0863db224cb6da/lib/sinatra/stoplight_admin.rb#L30
|
|
302
560
|
[sliding window]: https://github.com/bolshakov/stoplight#custom-window-size
|
|
561
|
+
[systems]: https://github.com/bolshakov/stoplight/blob/master/docs/systems.md
|
|
303
562
|
[Discussions forum]: https://github.com/bolshakov/stoplight/discussions/categories/q-a
|
|
@@ -5,18 +5,10 @@ module Stoplight
|
|
|
5
5
|
module Actions
|
|
6
6
|
# @abstract
|
|
7
7
|
class Action
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# @return lights_repository [Stoplight::Admin::LightsRepository]
|
|
14
|
-
def initialize(lights_repository:)
|
|
15
|
-
@lights_repository = lights_repository
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call(params)
|
|
19
|
-
raise NotImplementedError
|
|
8
|
+
# Unwinds to Sinatra's route dispatch via `throw :halt`, skipping the rest of the
|
|
9
|
+
# action and the route block, and using +status+ as the response status.
|
|
10
|
+
def halt(status)
|
|
11
|
+
throw :halt, status
|
|
20
12
|
end
|
|
21
13
|
end
|
|
22
14
|
end
|