shift-circuit-breaker 0.2.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +11 -0
  3. data/.gitignore +13 -0
  4. data/.reek +21 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +21 -0
  7. data/CONTRIBUTING.md +80 -0
  8. data/Gemfile +2 -0
  9. data/LICENSE +21 -0
  10. data/README.md +81 -0
  11. data/Rakefile +9 -0
  12. data/bin/console +7 -0
  13. data/bin/setup +7 -0
  14. data/config/newrelic.yml +28 -0
  15. data/config/rubocop/.layout_rubocop.yml +2 -0
  16. data/config/rubocop/.lint_rubocop.yml +47 -0
  17. data/config/rubocop/.metrics_rubocop.yml +39 -0
  18. data/config/rubocop/.naming_rubocop.yml +11 -0
  19. data/config/rubocop/.performance_rubocop.yml +60 -0
  20. data/config/rubocop/.style_rubocop.yml +154 -0
  21. data/lib/shift/circuit_breaker.rb +57 -0
  22. data/lib/shift/circuit_breaker/adapters/base_adapter.rb +13 -0
  23. data/lib/shift/circuit_breaker/adapters/newrelic_adapter.rb +13 -0
  24. data/lib/shift/circuit_breaker/adapters/sentry_adapter.rb +13 -0
  25. data/lib/shift/circuit_breaker/circuit_handler.rb +107 -0
  26. data/lib/shift/circuit_breaker/circuit_logger.rb +36 -0
  27. data/lib/shift/circuit_breaker/circuit_monitor.rb +32 -0
  28. data/lib/shift/circuit_breaker/config.rb +48 -0
  29. data/lib/shift/circuit_breaker/version.rb +7 -0
  30. data/shift-circuit-breaker.gemspec +40 -0
  31. data/spec/shift/adapters/base_adapter_spec.rb +21 -0
  32. data/spec/shift/adapters/newrelic_adapter_spec.rb +44 -0
  33. data/spec/shift/adapters/sentry_adapter_spec.rb +42 -0
  34. data/spec/shift/circuit_breaker/circuit_handler_exception_handling_spec.rb +142 -0
  35. data/spec/shift/circuit_breaker/circuit_handler_monitoring_spec.rb +72 -0
  36. data/spec/shift/circuit_breaker/circuit_handler_spec.rb +165 -0
  37. data/spec/shift/circuit_breaker/circuit_logger_spec.rb +67 -0
  38. data/spec/shift/circuit_breaker/circuit_monitor_spec.rb +52 -0
  39. data/spec/shift/circuit_breaker_spec.rb +73 -0
  40. data/spec/spec_helper.rb +25 -0
  41. metadata +230 -0
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Shift
6
+ module CircuitBreaker
7
+ describe CircuitLogger do
8
+ before do
9
+ allow($stdout).to receive(:write)
10
+ end
11
+
12
+ context "#error" do
13
+ it "logs the given error message" do
14
+ # Arrange
15
+ context = { circuit_name: :test_circuit_breaker, error_message: "timeout", state: :open }
16
+ error_message = (described_class::ERROR_MESSAGE % context)
17
+ logger_instance = ::Logger.new(STDOUT)
18
+ logger = described_class.new(logger: logger_instance)
19
+
20
+ allow(logger_instance).to receive(:error)
21
+
22
+ # Act
23
+ logger.error(context)
24
+
25
+ # Assert
26
+ expect(logger_instance).to have_received(:error).with(include(error_message))
27
+ end
28
+
29
+ it "logs the given error message using the provided remote_logger" do
30
+ # Arrange
31
+ context = { circuit_name: :test_circuit_breaker, error_message: "timeout", state: :open }
32
+ error_message = (described_class::ERROR_MESSAGE % context)
33
+ remote_logger = Shift::CircuitBreaker::Adapters::SentryAdapter
34
+ logger = described_class.new(remote_logger: remote_logger)
35
+
36
+ allow(remote_logger).to receive(:call)
37
+
38
+ # Act
39
+ logger.error(context)
40
+
41
+ # Assert
42
+ aggregate_failures do
43
+ expect(logger.remote_logger).to eq(remote_logger)
44
+ expect(remote_logger).to have_received(:call).with(include(error_message))
45
+ end
46
+ end
47
+ end
48
+
49
+ context "#info" do
50
+ it "logs the given input" do
51
+ # Arrange
52
+ message = "some message"
53
+ logger_instance = ::Logger.new(STDOUT)
54
+ logger = described_class.new(logger: logger_instance)
55
+
56
+ allow(logger_instance).to receive(:info)
57
+
58
+ # Act
59
+ logger.info(message)
60
+
61
+ # Assert
62
+ expect(logger_instance).to have_received(:info).with(include(message))
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Shift
6
+ module CircuitBreaker
7
+ describe CircuitMonitor do
8
+ before do
9
+ allow($stdout).to receive(:write)
10
+ end
11
+
12
+ context "#record_metric" do
13
+ it "logs the metric information" do
14
+ # Arrange
15
+ circuit_breaker_name = :test_circuit_breaker
16
+ circuit_breaker_state = :open
17
+ metric = "Custom/#{circuit_breaker_name.to_s.classify}CircuitBreaker/#{circuit_breaker_state.to_s.classify}"
18
+ logger_instance = ::Logger.new(STDOUT)
19
+ circuit_monitor = described_class.new(logger: logger_instance)
20
+
21
+ allow(logger_instance).to receive(:info)
22
+
23
+ # Act
24
+ circuit_monitor.record_metric(circuit_breaker_name, circuit_breaker_state)
25
+
26
+ # Assert
27
+ expect(logger_instance).to have_received(:info).with(include(metric))
28
+ end
29
+
30
+ it "logs the metric information using the provided monitor" do
31
+ # Arrange
32
+ circuit_breaker_name = :test_circuit_breaker
33
+ circuit_breaker_state = :open
34
+ metric = "Custom/#{circuit_breaker_name.to_s.classify}CircuitBreaker/#{circuit_breaker_state.to_s.classify}"
35
+ metric_monitor = Shift::CircuitBreaker::Adapters::NewRelicAdapter
36
+ circuit_monitor = described_class.new(monitor: metric_monitor)
37
+
38
+ allow(metric_monitor).to receive(:call)
39
+
40
+ # Act
41
+ circuit_monitor.record_metric(circuit_breaker_name, circuit_breaker_state)
42
+
43
+ # Assert
44
+ aggregate_failures do
45
+ expect(circuit_monitor.monitor).to eq(metric_monitor)
46
+ expect(metric_monitor).to have_received(:call).with(metric)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Shift
6
+ describe CircuitBreaker do
7
+ before do
8
+ allow($stdout).to receive(:write)
9
+ end
10
+
11
+ context "Configuration" do
12
+ it "correctly assigns the configuration keys" do
13
+ # Arrange
14
+ dummy_new_relic_license_key = SecureRandom.hex(10)
15
+ dummy_new_relic_app_name = "Test App"
16
+ dummy_sentry_dsn = SecureRandom.hex(10)
17
+ dummy_sentry_environments = %w[production]
18
+
19
+ # Act
20
+ Shift::CircuitBreaker.configure do |config|
21
+ config.new_relic_license_key = dummy_new_relic_license_key
22
+ config.new_relic_app_name = dummy_new_relic_app_name
23
+ config.sentry_dsn = dummy_sentry_dsn
24
+ config.sentry_environments = dummy_sentry_environments
25
+ end
26
+
27
+ config = Shift::CircuitBreaker.config
28
+
29
+ # Assert
30
+ aggregate_failures do
31
+ expect(config.new_relic_license_key).to eq(dummy_new_relic_license_key)
32
+ expect(config.new_relic_app_name).to eq(dummy_new_relic_app_name)
33
+ expect(config.sentry_dsn).to eq(dummy_sentry_dsn)
34
+ expect(config.sentry_environments).to include("production")
35
+ end
36
+ end
37
+ end
38
+
39
+ context "Instantiation" do
40
+ it "creates an instance of the circuit breaker" do
41
+ # Arrange
42
+ cb_name = :test_circuit
43
+ error_threshold = 10
44
+ skip_duration = 60
45
+ additional_exception_classes = [Faraday::ClientError]
46
+ logger_stub = instance_double("CustomLogger")
47
+ monitor_stub = instance_double("CustomMonitor")
48
+
49
+ # Act
50
+ cb_instance = Shift::CircuitBreaker.new(cb_name,
51
+ error_threshold: error_threshold,
52
+ skip_duration: skip_duration,
53
+ additional_exception_classes: additional_exception_classes,
54
+ logger: logger_stub,
55
+ monitor: monitor_stub)
56
+
57
+ # Assert
58
+ aggregate_failures do
59
+ expect(described_class).to be_a(Module)
60
+ expect(cb_instance).to be_a(Shift::CircuitBreaker::CircuitHandler)
61
+ expect(cb_instance.name).to eq(cb_name)
62
+ expect(cb_instance.error_threshold).to eq(error_threshold)
63
+ expect(cb_instance.skip_duration).to eq(skip_duration)
64
+ expect(cb_instance.exception_classes).to include(Faraday::ClientError)
65
+ expect(cb_instance.logger).to eq(logger_stub)
66
+ expect(cb_instance.monitor).to eq(monitor_stub)
67
+ expect(cb_instance.error_count).to eq(0)
68
+ expect(cb_instance.state).to eq(:closed)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/numeric/time"
4
+ require "rspec"
5
+ require "pry"
6
+ require "securerandom"
7
+ require "shift/circuit_breaker"
8
+ require "timecop"
9
+
10
+ ENV["RACK_ENV"] ||= "test"
11
+
12
+ Timecop.safe_mode = true
13
+
14
+ RSpec.configure do |config|
15
+ config.expect_with :rspec do |expectations|
16
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
17
+ expectations.on_potential_false_positives = :nothing
18
+ end
19
+
20
+ config.mock_with :rspec do |mocks|
21
+ mocks.verify_partial_doubles = true
22
+ end
23
+
24
+ config.shared_context_metadata_behavior = :apply_to_host_groups
25
+ end
metadata ADDED
@@ -0,0 +1,230 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shift-circuit-breaker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Mufudzi Masaire
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 5.1.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '5.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.1.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: newrelic_rpm
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.8'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 4.8.0.341
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '4.8'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 4.8.0.341
53
+ - !ruby/object:Gem::Dependency
54
+ name: sentry-raven
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.7'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.7.2
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '2.7'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.7.2
73
+ - !ruby/object:Gem::Dependency
74
+ name: bundler
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '1.16'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '1.16'
87
+ - !ruby/object:Gem::Dependency
88
+ name: pry
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: 0.11.3
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: 0.11.3
101
+ - !ruby/object:Gem::Dependency
102
+ name: rake
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '12.3'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '12.3'
115
+ - !ruby/object:Gem::Dependency
116
+ name: rspec
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '3.7'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '3.7'
129
+ - !ruby/object:Gem::Dependency
130
+ name: rubocop
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: 0.52.1
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: 0.52.1
143
+ - !ruby/object:Gem::Dependency
144
+ name: timecop
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: 0.9.1
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: 0.9.1
157
+ description: |2
158
+ The library provides a mechanism for detecting, monitoring and controlling external service calls that will
159
+ most-likely fail at some point (e.g. timeout) and cause request queuing, thus preventing cascading system failures.
160
+ email: mufudzi.masaire@shiftcommerce.com
161
+ executables:
162
+ - console
163
+ - setup
164
+ extensions: []
165
+ extra_rdoc_files: []
166
+ files:
167
+ - ".codeclimate.yml"
168
+ - ".gitignore"
169
+ - ".reek"
170
+ - ".rspec"
171
+ - ".rubocop.yml"
172
+ - CONTRIBUTING.md
173
+ - Gemfile
174
+ - LICENSE
175
+ - README.md
176
+ - Rakefile
177
+ - bin/console
178
+ - bin/setup
179
+ - config/newrelic.yml
180
+ - config/rubocop/.layout_rubocop.yml
181
+ - config/rubocop/.lint_rubocop.yml
182
+ - config/rubocop/.metrics_rubocop.yml
183
+ - config/rubocop/.naming_rubocop.yml
184
+ - config/rubocop/.performance_rubocop.yml
185
+ - config/rubocop/.style_rubocop.yml
186
+ - lib/shift/circuit_breaker.rb
187
+ - lib/shift/circuit_breaker/adapters/base_adapter.rb
188
+ - lib/shift/circuit_breaker/adapters/newrelic_adapter.rb
189
+ - lib/shift/circuit_breaker/adapters/sentry_adapter.rb
190
+ - lib/shift/circuit_breaker/circuit_handler.rb
191
+ - lib/shift/circuit_breaker/circuit_logger.rb
192
+ - lib/shift/circuit_breaker/circuit_monitor.rb
193
+ - lib/shift/circuit_breaker/config.rb
194
+ - lib/shift/circuit_breaker/version.rb
195
+ - shift-circuit-breaker.gemspec
196
+ - spec/shift/adapters/base_adapter_spec.rb
197
+ - spec/shift/adapters/newrelic_adapter_spec.rb
198
+ - spec/shift/adapters/sentry_adapter_spec.rb
199
+ - spec/shift/circuit_breaker/circuit_handler_exception_handling_spec.rb
200
+ - spec/shift/circuit_breaker/circuit_handler_monitoring_spec.rb
201
+ - spec/shift/circuit_breaker/circuit_handler_spec.rb
202
+ - spec/shift/circuit_breaker/circuit_logger_spec.rb
203
+ - spec/shift/circuit_breaker/circuit_monitor_spec.rb
204
+ - spec/shift/circuit_breaker_spec.rb
205
+ - spec/spec_helper.rb
206
+ homepage: https://github.com/shiftcommerce/shift-circuit-breaker
207
+ licenses:
208
+ - MIT
209
+ metadata: {}
210
+ post_install_message:
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: 2.3.0
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubyforge_project:
226
+ rubygems_version: 2.5.1
227
+ signing_key:
228
+ specification_version: 4
229
+ summary: A generic implementation of the Circuit Breaker pattern in Ruby
230
+ test_files: []