Skip to content

Commit 437ef4a

Browse files
authored
Merge pull request #32 from Fatsoma/ENG-549-log-channel-error
Hutch will log and notify on channel error
2 parents b2b2b26 + d9b6449 commit 437ef4a

2 files changed

Lines changed: 138 additions & 7 deletions

File tree

lib/hutch/channel_broker.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,25 @@ def open_channel
6363
logger.info 'enabling publisher confirms'
6464
ch.confirm_select
6565
end
66+
67+
# on_error handler logs and notifies any unhandled channel errors
68+
ch.on_error do |channel, method|
69+
logger.error "Channel error [channel=#{channel.inspect}, method=#{method.inspect}, active=#{active}]"
70+
71+
context = {method: method.inspect}
72+
if method.is_a?(AMQ::Protocol::Channel::Close)
73+
error_message = method.reply_text
74+
context[:reply_code] = method.reply_code
75+
else
76+
error_message = 'Channel error'
77+
end
78+
79+
::Honeybadger.notify(
80+
error_class: 'Hutch::ChannelBroker::OnError',
81+
error_message: error_message,
82+
context: context
83+
) if defined?(::Honeybadger)
84+
end
6685
end
6786
end
6887

spec/hutch/channel_broker_spec.rb

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
Hutch::Config.instance_variable_set(:@config, nil)
1212
Hutch::Config.initialize
1313
end
14-
let(:connection) { double('Connection') }
15-
let(:channel) { double('Channel') }
14+
let(:connection) { double(:connection) }
15+
let(:channel) { double(:channel) }
16+
1617
subject(:channel_broker) { Hutch::ChannelBroker.new(connection, config) }
1718

1819
shared_examples 'an empty channel broker' do
19-
%i(channel exchange default_wait_exchange wait_exchanges).each do |name|
20+
%i[channel exchange default_wait_exchange wait_exchanges].each do |name|
2021
it { expect(channel_broker.instance_variable_get("@#{name}")).to be_nil }
2122
end
2223
end
@@ -46,8 +47,8 @@
4647
end
4748

4849
describe '#reconnect' do
49-
let(:new_channel) { double('Channel') }
50-
let(:new_exchange) { double('Exchange') }
50+
let(:new_channel) { double(:new_channel) }
51+
let(:new_exchange) { double(:new_exchange) }
5152

5253
before do
5354
allow(channel_broker).to receive(:disconnect)
@@ -97,8 +98,8 @@
9798
describe '#declare_wait_exchange' do
9899
let(:expiration) { rand(1000..1_000_000) }
99100
let(:suffix) { expiration.to_s }
100-
let(:new_exchange) { double('Exchange') }
101-
let(:new_queue) { double('Queue') }
101+
let(:new_exchange) { double(:new_exchange) }
102+
let(:new_queue) { double(:new_queue) }
102103
let(:exchange_name) { "wait-exchange_#{suffix}" }
103104
let(:queue_name) { "wait-queue_#{suffix}" }
104105
let(:main_exchange_name) { 'main-exchange' }
@@ -129,4 +130,115 @@
129130
is_expected.to eq(new_exchange)
130131
end
131132
end
133+
134+
describe '#open_channel' do
135+
let(:channel) do
136+
Class.new do
137+
def on_error(&block)
138+
@on_error = block
139+
end
140+
def test_on_error(*args)
141+
@on_error.call(*args)
142+
end
143+
def confirm_select; end
144+
end.new
145+
end
146+
let(:honey_badger) { double(:honey_badger) }
147+
let(:config) { {} }
148+
let(:method) { Class.new }
149+
150+
before do
151+
stub_const('Honeybadger', honey_badger)
152+
allow(honey_badger).to receive(:add_breadcrumb)
153+
allow(honey_badger).to receive(:notify)
154+
allow(connection).to receive(:create_channel).and_return(channel)
155+
allow(connection).to receive(:prefetch_channel)
156+
allow(channel).to receive(:confirm_select)
157+
end
158+
159+
subject { channel_broker.open_channel }
160+
161+
context 'when no publisher_confirms or force_publisher_confirms option' do
162+
let(:config) { {} }
163+
164+
it do
165+
is_expected.to eq(channel)
166+
expect(channel).to_not have_received(:confirm_select)
167+
end
168+
end
169+
170+
context 'when publisher_confirms option' do
171+
let(:config) { { publisher_confirms: true } }
172+
173+
it do
174+
is_expected.to eq(channel)
175+
expect(channel).to have_received(:confirm_select)
176+
end
177+
end
178+
179+
context 'when force_publisher_confirms option' do
180+
let(:config) { { force_publisher_confirms: true } }
181+
182+
it do
183+
is_expected.to eq(channel)
184+
expect(channel).to have_received(:confirm_select)
185+
end
186+
end
187+
188+
context 'when on_error block is called' do
189+
let(:reply_code) { 406 }
190+
let(:reply_text) { 'delivery acknowledgement on channel 1 timed out' }
191+
let(:class_id) { 'test-class-id' }
192+
let(:method_id) { 'test-method-id' }
193+
194+
subject do
195+
channel_broker.open_channel.tap do |channel|
196+
channel.test_on_error(channel, method)
197+
end
198+
end
199+
200+
context 'when method is AMQ::Protocol::Channel::Close' do
201+
let(:method) do
202+
AMQ::Protocol::Channel::Close.new(reply_code, reply_text, class_id, method_id)
203+
end
204+
let(:context) do
205+
{
206+
reply_code: reply_code,
207+
method: method.inspect
208+
}
209+
end
210+
211+
it do
212+
is_expected.to eq(channel)
213+
expect(honey_badger).to have_received(:notify)
214+
.with(
215+
error_class: 'Hutch::ChannelBroker::OnError',
216+
error_message: reply_text,
217+
context: context
218+
)
219+
end
220+
end
221+
222+
context 'when method is AMQ::Protocol::Channel::CloseOk' do
223+
let(:method) do
224+
AMQ::Protocol::Channel::CloseOk.new
225+
end
226+
let(:context) do
227+
{
228+
method: method.inspect
229+
}
230+
end
231+
232+
it do
233+
is_expected.to eq(channel)
234+
expect(honey_badger).to have_received(:notify)
235+
.with(
236+
error_class: 'Hutch::ChannelBroker::OnError',
237+
error_message: 'Channel error',
238+
context: context
239+
)
240+
end
241+
end
242+
end
243+
end
132244
end

0 commit comments

Comments
 (0)