hutch 0.18.0 → 1.1.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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +20 -8
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +466 -2
  7. data/Gemfile +18 -4
  8. data/Guardfile +13 -4
  9. data/LICENSE +2 -1
  10. data/README.md +397 -32
  11. data/Rakefile +8 -1
  12. data/bin/ci/before_build.sh +20 -0
  13. data/bin/ci/install_on_debian.sh +46 -0
  14. data/hutch.gemspec +6 -7
  15. data/lib/hutch/acknowledgements/base.rb +16 -0
  16. data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
  17. data/lib/hutch/adapters/march_hare.rb +1 -1
  18. data/lib/hutch/broker.rb +127 -103
  19. data/lib/hutch/cli.rb +66 -25
  20. data/lib/hutch/config.rb +230 -55
  21. data/lib/hutch/consumer.rb +42 -3
  22. data/lib/hutch/error_handlers/airbrake.rb +44 -16
  23. data/lib/hutch/error_handlers/base.rb +15 -0
  24. data/lib/hutch/error_handlers/bugsnag.rb +30 -0
  25. data/lib/hutch/error_handlers/honeybadger.rb +33 -18
  26. data/lib/hutch/error_handlers/logger.rb +12 -6
  27. data/lib/hutch/error_handlers/rollbar.rb +28 -0
  28. data/lib/hutch/error_handlers/sentry.rb +15 -12
  29. data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
  30. data/lib/hutch/error_handlers.rb +3 -0
  31. data/lib/hutch/exceptions.rb +8 -1
  32. data/lib/hutch/logging.rb +5 -5
  33. data/lib/hutch/message.rb +2 -4
  34. data/lib/hutch/publisher.rb +75 -0
  35. data/lib/hutch/serializers/identity.rb +19 -0
  36. data/lib/hutch/serializers/json.rb +22 -0
  37. data/lib/hutch/tracers/datadog.rb +17 -0
  38. data/lib/hutch/tracers.rb +1 -0
  39. data/lib/hutch/version.rb +1 -2
  40. data/lib/hutch/waiter.rb +104 -0
  41. data/lib/hutch/worker.rb +81 -75
  42. data/lib/hutch.rb +15 -6
  43. data/lib/yard-settings/handler.rb +38 -0
  44. data/lib/yard-settings/yard-settings.rb +2 -0
  45. data/spec/hutch/broker_spec.rb +162 -77
  46. data/spec/hutch/cli_spec.rb +16 -3
  47. data/spec/hutch/config_spec.rb +121 -22
  48. data/spec/hutch/consumer_spec.rb +82 -4
  49. data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
  50. data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
  51. data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
  52. data/spec/hutch/error_handlers/logger_spec.rb +14 -1
  53. data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
  54. data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
  55. data/spec/hutch/error_handlers/sentry_spec.rb +21 -2
  56. data/spec/hutch/logger_spec.rb +12 -6
  57. data/spec/hutch/message_spec.rb +2 -2
  58. data/spec/hutch/serializers/json_spec.rb +17 -0
  59. data/spec/hutch/tracers/datadog_spec.rb +44 -0
  60. data/spec/hutch/waiter_spec.rb +51 -0
  61. data/spec/hutch/worker_spec.rb +89 -5
  62. data/spec/spec_helper.rb +7 -5
  63. data/templates/default/class/html/settings.erb +0 -0
  64. data/templates/default/class/setup.rb +4 -0
  65. data/templates/default/fulldoc/html/css/hutch.css +13 -0
  66. data/templates/default/layout/html/setup.rb +7 -0
  67. data/templates/default/method_details/html/settings.erb +5 -0
  68. data/templates/default/method_details/setup.rb +4 -0
  69. data/templates/default/method_details/text/settings.erb +0 -0
  70. data/templates/default/module/html/settings.erb +40 -0
  71. data/templates/default/module/setup.rb +4 -0
  72. metadata +62 -43
  73. data/circle.yml +0 -3
@@ -28,6 +28,32 @@ describe Hutch::Consumer do
28
28
  ComplexConsumer
29
29
  end
30
30
 
31
+ let(:consumer_using_quorum_queue) do
32
+ unless defined? ConsumerUsingQuorumQueue
33
+ class ConsumerUsingQuorumQueue
34
+ include Hutch::Consumer
35
+ consume 'hutch.test1'
36
+ arguments foo: :bar
37
+
38
+ quorum_queue
39
+ end
40
+ end
41
+ ConsumerUsingQuorumQueue
42
+ end
43
+
44
+ let(:consumer_using_classic_queue) do
45
+ unless defined? ConsumerUsingLazyQueue
46
+ class ConsumerUsingLazyQueue
47
+ include Hutch::Consumer
48
+ consume 'hutch.test1'
49
+ arguments foo: :bar
50
+ lazy_queue
51
+ classic_queue
52
+ end
53
+ end
54
+ ConsumerUsingLazyQueue
55
+ end
56
+
31
57
  describe 'module inclusion' do
32
58
  it 'registers the class as a consumer' do
33
59
  expect(Hutch).to receive(:register_consumer) do |klass|
@@ -71,6 +97,43 @@ describe Hutch::Consumer do
71
97
  end
72
98
  end
73
99
 
100
+ describe 'default queue mode' do
101
+ it 'does not specify any mode by default' do
102
+ expect(simple_consumer.queue_mode).to eq(nil)
103
+ expect(simple_consumer.queue_type).to eq(nil)
104
+ end
105
+ end
106
+
107
+ describe '.lazy_queue' do
108
+ context 'when queue mode has been set explicitly to lazy' do
109
+ it 'sets queue mode to lazy' do
110
+ expect(consumer_using_classic_queue.queue_mode).to eq('lazy')
111
+ end
112
+ end
113
+ end
114
+
115
+ describe '.classic_queue' do
116
+ context 'when queue type has been set explicitly to classic' do
117
+ it 'sets queue type to classic' do
118
+ expect(consumer_using_classic_queue.queue_type).to eq('classic')
119
+ end
120
+ end
121
+ end
122
+
123
+ describe '.quorum_queue' do
124
+ context 'when queue type has been set explicitly to quorum' do
125
+ it 'sets queue type to quorum' do
126
+ expect(consumer_using_quorum_queue.queue_type).to eq('quorum')
127
+ end
128
+
129
+ it 'accepts initial group size as an option' do
130
+ consumer = simple_consumer
131
+ expect { consumer.quorum_queue(initial_group_size: 3) }
132
+ .to change { consumer.initial_group_size }.to(3)
133
+ end
134
+ end
135
+ end
136
+
74
137
  describe '.arguments' do
75
138
  let(:args) { { foo: :bar} }
76
139
 
@@ -82,15 +145,30 @@ describe Hutch::Consumer do
82
145
  end
83
146
 
84
147
  describe '.get_arguments' do
85
-
86
148
  context 'when defined' do
87
- it { expect(complex_consumer.get_arguments).to eq(foo: :bar) }
149
+ it { expect(complex_consumer.get_arguments).to include(foo: :bar) }
88
150
  end
89
151
 
90
- context 'when not defined' do
91
- it { expect(simple_consumer.get_arguments).to eq({}) }
152
+ context 'when queue is lazy' do
153
+ it 'has the x-queue-mode argument set to lazy' do
154
+ expect(consumer_using_classic_queue.get_arguments['x-queue-mode'])
155
+ .to eq('lazy')
156
+ end
92
157
  end
93
158
 
159
+ context "when queue's type is quorum" do
160
+ let(:arguments) { consumer_using_quorum_queue.get_arguments }
161
+ it 'has the x-queue-type argument set to quorum' do
162
+ expect(arguments['x-queue-type']).to eq('quorum')
163
+ expect(arguments).to_not have_key('x-quorum-initial-group-size')
164
+ end
165
+
166
+ it 'has the x-quorum-initial-group-size argument set to quorum' do
167
+ consumer_using_quorum_queue.quorum_queue(initial_group_size: 5)
168
+ expect(arguments['x-queue-type']).to eq('quorum')
169
+ expect(arguments['x-quorum-initial-group-size']).to eq(5)
170
+ end
171
+ end
94
172
  end
95
173
 
96
174
  describe '.get_queue_name' do
@@ -14,21 +14,36 @@ describe Hutch::ErrorHandlers::Airbrake do
14
14
 
15
15
  it "logs the error to Airbrake" do
16
16
  message_id = "1"
17
+ properties = OpenStruct.new(message_id: message_id)
17
18
  payload = "{}"
18
19
  consumer = double
19
20
  ex = error
20
21
  message = {
21
- :error_class => ex.class.name,
22
- :error_message => "#{ ex.class.name }: #{ ex.message }",
23
- :backtrace => ex.backtrace,
24
- :parameters => {
25
- :payload => payload,
26
- :consumer => consumer,
27
- },
28
- :cgi_data => ENV.to_hash,
22
+ payload: payload,
23
+ consumer: consumer,
24
+ cgi_data: ENV.to_hash,
29
25
  }
30
- expect(::Airbrake).to receive(:notify_or_ignore).with(ex, message)
31
- error_handler.handle(message_id, payload, consumer, ex)
26
+ expect(::Airbrake).to receive(:notify).with(ex, message)
27
+ error_handler.handle(properties, payload, consumer, ex)
28
+ end
29
+ end
30
+
31
+ describe '#handle_setup_exception' do
32
+ let(:error) do
33
+ begin
34
+ raise "Stuff went wrong"
35
+ rescue RuntimeError => err
36
+ err
37
+ end
38
+ end
39
+
40
+ it "logs the error to Airbrake" do
41
+ ex = error
42
+ message = {
43
+ cgi_data: ENV.to_hash,
44
+ }
45
+ expect(::Airbrake).to receive(:notify).with(ex, message)
46
+ error_handler.handle_setup_exception(ex)
32
47
  end
33
48
  end
34
49
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Hutch::ErrorHandlers::Bugsnag do
6
+ let(:error_handler) { described_class.new }
7
+
8
+ before do
9
+ Bugsnag.configure do |bugsnag|
10
+ bugsnag.api_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
11
+ end
12
+ end
13
+
14
+ describe "#handle" do
15
+ let(:error) do
16
+ begin
17
+ raise "Stuff went wrong"
18
+ rescue RuntimeError => err
19
+ err
20
+ end
21
+ end
22
+
23
+ it "logs the error to Bugsnag" do
24
+ message_id = "1"
25
+ properties = OpenStruct.new(message_id: message_id)
26
+ payload = "{}"
27
+ consumer = double
28
+ ex = error
29
+ message = {
30
+ payload: payload,
31
+ consumer: consumer
32
+ }
33
+
34
+ expect(::Bugsnag).to receive(:notify).with(ex).and_call_original
35
+ expect_any_instance_of(::Bugsnag::Report).to receive(:add_tab).with(:hutch, message)
36
+ error_handler.handle(properties, payload, consumer, ex)
37
+ end
38
+ end
39
+
40
+ describe "#handle_setup_exception" do
41
+ let(:error) do
42
+ begin
43
+ raise "Stuff went wrong"
44
+ rescue RuntimeError => err
45
+ err
46
+ end
47
+ end
48
+
49
+ it "logs the error to Bugsnag" do
50
+ ex = error
51
+ expect(::Bugsnag).to receive(:notify).with(ex)
52
+ error_handler.handle_setup_exception(ex)
53
+ end
54
+ end
55
+ end
@@ -14,6 +14,7 @@ describe Hutch::ErrorHandlers::Honeybadger do
14
14
 
15
15
  it "logs the error to Honeybadger" do
16
16
  message_id = "1"
17
+ properties = OpenStruct.new(message_id: message_id)
17
18
  payload = "{}"
18
19
  consumer = double
19
20
  ex = error
@@ -29,8 +30,29 @@ describe Hutch::ErrorHandlers::Honeybadger do
29
30
  :payload => payload
30
31
  }
31
32
  }
32
- expect(::Honeybadger).to receive(:notify_or_ignore).with(message)
33
- error_handler.handle(message_id, payload, consumer, ex)
33
+ expect(error_handler).to receive(:notify_honeybadger).with(message)
34
+ error_handler.handle(properties, payload, consumer, ex)
35
+ end
36
+ end
37
+
38
+ describe '#handle_setup_exception' do
39
+ let(:error) do
40
+ begin
41
+ raise "Stuff went wrong during setup"
42
+ rescue RuntimeError => err
43
+ err
44
+ end
45
+ end
46
+
47
+ it "logs the error to Honeybadger" do
48
+ ex = error
49
+ message = {
50
+ :error_class => ex.class.name,
51
+ :error_message => "#{ ex.class.name }: #{ ex.message }",
52
+ :backtrace => ex.backtrace,
53
+ }
54
+ expect(error_handler).to receive(:notify_honeybadger).with(message)
55
+ error_handler.handle_setup_exception(ex)
34
56
  end
35
57
  end
36
58
  end
@@ -4,12 +4,25 @@ describe Hutch::ErrorHandlers::Logger do
4
4
  let(:error_handler) { Hutch::ErrorHandlers::Logger.new }
5
5
 
6
6
  describe '#handle' do
7
+ let(:properties) { OpenStruct.new(message_id: "1") }
8
+ let(:payload) { "{}" }
7
9
  let(:error) { double(message: "Stuff went wrong", class: "RuntimeError",
8
10
  backtrace: ["line 1", "line 2"]) }
9
11
 
10
12
  it "logs three separate lines" do
11
13
  expect(Hutch::Logging.logger).to receive(:error).exactly(3).times
12
- error_handler.handle("1", "{}", double, error)
14
+ error_handler.handle(properties, payload, double, error)
15
+ end
16
+ end
17
+
18
+ describe '#handle_setup_exception' do
19
+ let(:error) { double(message: "Stuff went wrong during setup",
20
+ class: "RuntimeError",
21
+ backtrace: ["line 1", "line 2"]) }
22
+
23
+ it "logs two separate lines" do
24
+ expect(Hutch::Logging.logger).to receive(:error).exactly(2).times
25
+ error_handler.handle_setup_exception(error)
13
26
  end
14
27
  end
15
28
  end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Rollbar do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Rollbar.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Rollbar" do
16
+ message_id = "1"
17
+ properties = OpenStruct.new(message_id: message_id)
18
+ payload = "{}"
19
+ consumer = double
20
+ ex = error
21
+ message = {
22
+ payload: payload,
23
+ consumer: consumer
24
+ }
25
+ expect(::Rollbar).to receive(:error).with(ex, message)
26
+ error_handler.handle(properties, payload, consumer, ex)
27
+ end
28
+ end
29
+
30
+ describe '#handle_setup_exception' do
31
+ let(:error) do
32
+ begin
33
+ raise "Stuff went wrong"
34
+ rescue RuntimeError => err
35
+ err
36
+ end
37
+ end
38
+
39
+ it "logs the error to Rollbar" do
40
+ ex = error
41
+ expect(::Rollbar).to receive(:error).with(ex)
42
+ error_handler.handle_setup_exception(ex)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::SentryRaven do
4
+ let(:error_handler) { Hutch::ErrorHandlers::SentryRaven.new }
5
+
6
+ describe '#handle' do
7
+ let(:properties) { OpenStruct.new(message_id: "1") }
8
+ let(:payload) { "{}" }
9
+ let(:error) do
10
+ begin
11
+ raise "Stuff went wrong"
12
+ rescue RuntimeError => err
13
+ err
14
+ end
15
+ end
16
+
17
+ it "logs the error to Sentry" do
18
+ expect(Raven).to receive(:capture_exception).with(error, extra: { payload: payload })
19
+ error_handler.handle(properties, payload, double, error)
20
+ end
21
+ end
22
+
23
+ describe '#handle_setup_exception' do
24
+ let(:error) do
25
+ begin
26
+ raise "Stuff went wrong during setup"
27
+ rescue RuntimeError => err
28
+ err
29
+ end
30
+ end
31
+
32
+ it "logs the error to Sentry" do
33
+ expect(Raven).to receive(:capture_exception).with(error)
34
+ error_handler.handle_setup_exception(error)
35
+ end
36
+ end
37
+ end
@@ -4,6 +4,8 @@ describe Hutch::ErrorHandlers::Sentry do
4
4
  let(:error_handler) { Hutch::ErrorHandlers::Sentry.new }
5
5
 
6
6
  describe '#handle' do
7
+ let(:properties) { OpenStruct.new(message_id: "1") }
8
+ let(:payload) { "{}" }
7
9
  let(:error) do
8
10
  begin
9
11
  raise "Stuff went wrong"
@@ -13,8 +15,25 @@ describe Hutch::ErrorHandlers::Sentry do
13
15
  end
14
16
 
15
17
  it "logs the error to Sentry" do
16
- expect(Raven).to receive(:capture_exception).with(error)
17
- error_handler.handle("1", "{}", double, error)
18
+ expect(::Sentry).to receive(:capture_exception).with(error).and_call_original
19
+
20
+ error_handler.handle(properties, payload, double, error)
21
+ end
22
+ end
23
+
24
+ describe '#handle_setup_exception' do
25
+ let(:error) do
26
+ begin
27
+ raise "Stuff went wrong during setup"
28
+ rescue RuntimeError => err
29
+ err
30
+ end
31
+ end
32
+
33
+ it "logs the error to Sentry" do
34
+ expect(::Sentry).to receive(:capture_exception).with(error).and_call_original
35
+
36
+ error_handler.handle_setup_exception(error)
18
37
  end
19
38
  end
20
39
  end
@@ -3,24 +3,30 @@ require 'spec_helper'
3
3
  describe Hutch::Logging do
4
4
  let(:dummy_object) do
5
5
  class DummyObject
6
- include Hutch::Logging
6
+ include described_class
7
7
  end
8
8
  end
9
9
 
10
10
  describe '#logger' do
11
+ around do |example|
12
+ old_logger = described_class.logger
13
+ described_class.setup_logger
14
+ example.run
15
+ described_class.logger = old_logger
16
+ end
17
+
11
18
  context 'with the default logger' do
12
- subject { Hutch::Logging.logger }
19
+ subject { described_class.logger }
13
20
 
14
21
  it { is_expected.to be_instance_of(Logger) }
15
22
  end
16
23
 
17
24
  context 'with a custom logger' do
18
- let(:dummy_logger) { double("Dummy logger", warn: true, info: true) }
19
- after { Hutch::Logging.setup_logger }
25
+ let(:dummy_logger) { double("Dummy logger") }
20
26
 
21
27
  it "users the custom logger" do
22
- Hutch::Logging.logger = dummy_logger
23
- expect(Hutch::Logging.logger).to eq(dummy_logger)
28
+ described_class.logger = dummy_logger
29
+ expect(described_class.logger).to eq(dummy_logger)
24
30
  end
25
31
  end
26
32
  end
@@ -2,10 +2,10 @@ require 'hutch/message'
2
2
 
3
3
  describe Hutch::Message do
4
4
  let(:delivery_info) { double('Delivery Info') }
5
- let(:props) { double('Properties') }
5
+ let(:props) { double('Properties', content_type: "application/json") }
6
6
  let(:body) {{ foo: 'bar' }.with_indifferent_access}
7
7
  let(:json_body) { MultiJson.dump(body) }
8
- subject(:message) { Hutch::Message.new(delivery_info, props, json_body) }
8
+ subject(:message) { Hutch::Message.new(delivery_info, props, json_body, Hutch::Config[:serializer]) }
9
9
 
10
10
  describe '#body' do
11
11
  subject { super().body }
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::Serializers::JSON do
4
+ let(:subject) { described_class }
5
+
6
+ it "encode/decode" do
7
+ payload = { a: 1, b: 2 }
8
+ encoded = subject.encode(payload)
9
+ decoded = subject.decode(encoded)
10
+
11
+ expect(encoded).to eq "{\"a\":1,\"b\":2}"
12
+ expect(decoded).to eq("a" => 1, "b" => 2)
13
+ expect(decoded[:a]).to eq 1
14
+ expect(decoded[:b]).to eq 2
15
+ end
16
+
17
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Hutch::Tracers::Datadog do
4
+ describe "#handle" do
5
+ subject(:handle) { tracer.handle(message) }
6
+
7
+ let(:tracer) { described_class.new(klass) }
8
+ let(:klass) do
9
+ Class.new do
10
+ attr_reader :message
11
+
12
+ def initialize
13
+ @message = nil
14
+ end
15
+
16
+ def class
17
+ OpenStruct.new(name: 'ClassName')
18
+ end
19
+
20
+ def process(message)
21
+ @message = message
22
+ end
23
+ end.new
24
+ end
25
+ let(:message) { double(:message) }
26
+
27
+ before do
28
+ allow(Datadog.tracer).to receive(:trace).and_call_original
29
+ end
30
+
31
+ it 'uses Datadog tracer' do
32
+ handle
33
+
34
+ expect(Datadog.tracer).to have_received(:trace).with('ClassName',
35
+ hash_including(service: 'hutch', span_type: 'rabbitmq'))
36
+ end
37
+
38
+ it 'processes the message' do
39
+ expect {
40
+ handle
41
+ }.to change { klass.message }.from(nil).to(message)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require 'hutch/waiter'
2
+
3
+ RSpec.describe Hutch::Waiter do
4
+ describe '.wait_until_signaled' do
5
+ let(:pid) { Process.pid }
6
+ def start_kill_thread(signal)
7
+ Thread.new do
8
+ # sleep allows the worker time to set up the signal handling
9
+ # before the kill signal is sent.
10
+ sleep 0.001
11
+ Process.kill signal, pid
12
+ end
13
+ end
14
+
15
+ context 'a QUIT signal is received', if: RSpec::Support::Ruby.mri? do
16
+ it 'logs that hutch is stopping' do
17
+ expect(Hutch::Logging.logger).to receive(:info)
18
+ .with('caught SIGQUIT, stopping hutch...')
19
+
20
+ start_kill_thread('QUIT')
21
+ described_class.wait_until_signaled
22
+ end
23
+ end
24
+
25
+ context 'a TERM signal is received', if: !defined?(JRUBY_VERSION) do
26
+ it 'logs that hutch is stopping' do
27
+ expect(Hutch::Logging.logger).to receive(:info)
28
+ .with('caught SIGTERM, stopping hutch...')
29
+
30
+ start_kill_thread('TERM')
31
+ described_class.wait_until_signaled
32
+ end
33
+ end
34
+
35
+ context 'a INT signal is received', if: !defined?(JRUBY_VERSION) do
36
+ it 'logs that hutch is stopping' do
37
+ expect(Hutch::Logging.logger).to receive(:info)
38
+ .with('caught SIGINT, stopping hutch...')
39
+
40
+ start_kill_thread('INT')
41
+ described_class.wait_until_signaled
42
+ end
43
+ end
44
+ end
45
+
46
+ describe described_class::SHUTDOWN_SIGNALS do
47
+ it 'includes only things in Signal.list.keys' do
48
+ expect(described_class).to eq(described_class & Signal.list.keys)
49
+ end
50
+ end
51
+ end