hutch 0.25.0-java → 2.0.0.rc1-java
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 +402 -5
- data/LICENSE +1 -0
- data/README.md +157 -47
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/before_build_docker.sh +15 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +1 -1
- data/lib/hutch/adapters/bunny.rb +29 -1
- data/lib/hutch/adapters/march_hare.rb +24 -0
- data/lib/hutch/broker.rb +98 -35
- data/lib/hutch/cli.rb +16 -12
- data/lib/hutch/config.rb +42 -13
- data/lib/hutch/consumer.rb +63 -4
- data/lib/hutch/error_handlers/bugsnag.rb +30 -0
- data/lib/hutch/error_handlers/{opbeat.rb → rollbar.rb} +11 -13
- data/lib/hutch/error_handlers/sentry.rb +6 -11
- data/lib/hutch/error_handlers.rb +2 -1
- data/lib/hutch/publisher.rb +1 -1
- data/lib/hutch/serializers/json.rb +3 -3
- data/lib/hutch/tracers/datadog.rb +18 -0
- data/lib/hutch/tracers.rb +1 -1
- data/lib/hutch/version.rb +1 -2
- data/lib/hutch/worker.rb +46 -9
- data/lib/hutch.rb +8 -4
- data/lib/yard-settings/yard-settings.rb +2 -2
- data/spec/hutch/broker_spec.rb +192 -14
- data/spec/hutch/cli_spec.rb +12 -9
- data/spec/hutch/config_spec.rb +84 -12
- data/spec/hutch/consumer_spec.rb +93 -4
- data/spec/hutch/error_handlers/airbrake_spec.rb +4 -1
- data/spec/hutch/error_handlers/bugsnag_spec.rb +56 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +1 -1
- data/spec/hutch/error_handlers/logger_spec.rb +1 -1
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +13 -3
- data/spec/hutch/message_spec.rb +1 -1
- data/spec/hutch/tracers/datadog_spec.rb +46 -0
- data/spec/hutch/waiter_spec.rb +2 -2
- data/spec/hutch/worker_spec.rb +138 -24
- data/spec/integration/channel_recovery_spec.rb +187 -0
- data/spec/integration/publish_consume_spec.rb +47 -0
- metadata +32 -59
- data/.gitignore +0 -10
- data/.rspec +0 -1
- data/.travis.yml +0 -16
- data/.yardopts +0 -5
- data/Gemfile +0 -33
- data/Guardfile +0 -14
- data/Rakefile +0 -21
- data/examples/consumer.rb +0 -13
- data/examples/producer.rb +0 -10
- data/hutch.gemspec +0 -29
- data/lib/hutch/tracers/opbeat.rb +0 -37
- data/spec/hutch/error_handlers/opbeat_spec.rb +0 -37
- data/spec/spec_helper.rb +0 -42
- data/spec/tracers/opbeat_spec.rb +0 -44
data/lib/hutch/broker.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'uri'
|
|
1
2
|
require 'active_support/core_ext/object/blank'
|
|
2
3
|
|
|
3
4
|
require 'carrot-top'
|
|
@@ -113,6 +114,8 @@ module Hutch
|
|
|
113
114
|
logger.info 'enabling publisher confirms'
|
|
114
115
|
ch.confirm_select
|
|
115
116
|
end
|
|
117
|
+
|
|
118
|
+
connection.install_channel_recovery(ch)
|
|
116
119
|
end
|
|
117
120
|
end
|
|
118
121
|
|
|
@@ -120,13 +123,23 @@ module Hutch
|
|
|
120
123
|
@channel = open_channel
|
|
121
124
|
end
|
|
122
125
|
|
|
126
|
+
# Closing the old channel makes RabbitMQ forget the consumers on it
|
|
127
|
+
# and requeue their unacknowledged deliveries.
|
|
128
|
+
def replace_channel!
|
|
129
|
+
close_consumer_channel
|
|
130
|
+
open_channel!
|
|
131
|
+
declare_exchange!
|
|
132
|
+
declare_publisher!
|
|
133
|
+
end
|
|
134
|
+
|
|
123
135
|
def declare_exchange(ch = channel)
|
|
124
136
|
exchange_name = @config[:mq_exchange]
|
|
137
|
+
exchange_type = @config[:mq_exchange_type]
|
|
125
138
|
exchange_options = { durable: true }.merge(@config[:mq_exchange_options])
|
|
126
139
|
logger.info "using topic exchange '#{exchange_name}'"
|
|
127
140
|
|
|
128
141
|
with_bunny_precondition_handler('exchange') do
|
|
129
|
-
|
|
142
|
+
Adapter.new_exchange(ch, exchange_type, exchange_name, exchange_options)
|
|
130
143
|
end
|
|
131
144
|
end
|
|
132
145
|
|
|
@@ -169,24 +182,35 @@ module Hutch
|
|
|
169
182
|
@config[:tracer] && @config[:tracer] != Hutch::Tracers::NullTracer
|
|
170
183
|
end
|
|
171
184
|
|
|
172
|
-
# Create / get a durable queue
|
|
173
|
-
def queue(name,
|
|
185
|
+
# Create / get a durable queue.
|
|
186
|
+
def queue(name, options = {})
|
|
174
187
|
with_bunny_precondition_handler('queue') do
|
|
175
|
-
|
|
176
|
-
name = name.prepend(namespace + ":") if namespace.present?
|
|
177
|
-
channel.queue(name, durable: true, arguments: arguments)
|
|
188
|
+
channel.queue(name, **options)
|
|
178
189
|
end
|
|
179
190
|
end
|
|
180
191
|
|
|
192
|
+
def queue_exists?(name)
|
|
193
|
+
connection.queue_exists?(name)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Apply the configured namespace prefix to a queue name.
|
|
197
|
+
def namespaced_queue_name(name)
|
|
198
|
+
namespace = @config[:namespace].to_s.downcase.gsub(/[^-_:\.\w]/, "")
|
|
199
|
+
namespace.present? ? "#{namespace}:#{name}" : name
|
|
200
|
+
end
|
|
201
|
+
|
|
181
202
|
# Return a mapping of queue names to the routing keys they're bound to.
|
|
182
203
|
def bindings
|
|
183
204
|
results = Hash.new { |hash, key| hash[key] = [] }
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
|
|
206
|
+
filtered = api_client.bindings.
|
|
207
|
+
reject { |b| b['destination'] == b['routing_key'] }.
|
|
208
|
+
select { |b| b['source'] == @config[:mq_exchange] && b['vhost'] == vhost }
|
|
209
|
+
|
|
210
|
+
filtered.each do |binding|
|
|
188
211
|
results[binding['destination']] << binding['routing_key']
|
|
189
212
|
end
|
|
213
|
+
|
|
190
214
|
results
|
|
191
215
|
end
|
|
192
216
|
|
|
@@ -194,8 +218,8 @@ module Hutch
|
|
|
194
218
|
def unbind_redundant_bindings(queue, routing_keys)
|
|
195
219
|
return unless http_api_use_enabled?
|
|
196
220
|
|
|
197
|
-
bindings.
|
|
198
|
-
|
|
221
|
+
filtered = bindings.select { |dest, keys| dest == queue.name }
|
|
222
|
+
filtered.each do |dest, keys|
|
|
199
223
|
keys.reject { |key| routing_keys.include?(key) }.each do |key|
|
|
200
224
|
logger.debug "removing redundant binding #{queue.name} <--> #{key}"
|
|
201
225
|
queue.unbind(exchange, routing_key: key)
|
|
@@ -220,29 +244,26 @@ module Hutch
|
|
|
220
244
|
if defined?(JRUBY_VERSION)
|
|
221
245
|
channel.close
|
|
222
246
|
else
|
|
223
|
-
|
|
224
|
-
channel_work_pool.shutdown
|
|
225
|
-
# Give `timeout` seconds to jobs that are still being processed
|
|
226
|
-
channel_work_pool.join(@config[:graceful_exit_timeout])
|
|
227
|
-
# If after `timeout` they are still running, they are killed
|
|
228
|
-
channel_work_pool.kill
|
|
247
|
+
drain_consumer_work_pool
|
|
229
248
|
end
|
|
230
249
|
end
|
|
231
250
|
|
|
232
|
-
|
|
233
|
-
|
|
251
|
+
# Delivery tags are scoped to their channel: a tag from a replaced one
|
|
252
|
+
# is dropped, the server has requeued that delivery anyway.
|
|
253
|
+
def requeue(delivery_tag, channel: self.channel)
|
|
254
|
+
channel.reject(delivery_tag, true) if current_channel?(channel)
|
|
234
255
|
end
|
|
235
256
|
|
|
236
|
-
def reject(delivery_tag, requeue=false)
|
|
237
|
-
channel.reject(delivery_tag, requeue)
|
|
257
|
+
def reject(delivery_tag, requeue = false, channel: self.channel)
|
|
258
|
+
channel.reject(delivery_tag, requeue) if current_channel?(channel)
|
|
238
259
|
end
|
|
239
260
|
|
|
240
|
-
def ack(delivery_tag)
|
|
241
|
-
channel.ack(delivery_tag, false)
|
|
261
|
+
def ack(delivery_tag, channel: self.channel)
|
|
262
|
+
channel.ack(delivery_tag, false) if current_channel?(channel)
|
|
242
263
|
end
|
|
243
264
|
|
|
244
|
-
def nack(delivery_tag)
|
|
245
|
-
channel.nack(delivery_tag, false, false)
|
|
265
|
+
def nack(delivery_tag, channel: self.channel)
|
|
266
|
+
channel.nack(delivery_tag, false, false) if current_channel?(channel)
|
|
246
267
|
end
|
|
247
268
|
|
|
248
269
|
def publish(*args)
|
|
@@ -264,15 +285,39 @@ module Hutch
|
|
|
264
285
|
|
|
265
286
|
private
|
|
266
287
|
|
|
288
|
+
def current_channel?(ch)
|
|
289
|
+
ch.equal?(channel)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def close_consumer_channel
|
|
293
|
+
return unless @channel && @channel.open?
|
|
294
|
+
|
|
295
|
+
drain_consumer_work_pool unless defined?(JRUBY_VERSION)
|
|
296
|
+
@channel.close
|
|
297
|
+
rescue Hutch::Adapter::ChannelAlreadyClosed
|
|
298
|
+
# The server closed the channel first: same outcome.
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Bunny kills the consumer work pool when the channel closes. Draining
|
|
302
|
+
# first gives the running handlers `graceful_exit_timeout` to finish.
|
|
303
|
+
def drain_consumer_work_pool
|
|
304
|
+
channel_work_pool.shutdown
|
|
305
|
+
channel_work_pool.join(@config[:graceful_exit_timeout])
|
|
306
|
+
channel_work_pool.kill
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
Config = Struct.new(:host, :port, :username, :password, :ssl, :protocol, :sanitized_uri)
|
|
310
|
+
private_constant :Config
|
|
311
|
+
|
|
267
312
|
def api_config
|
|
268
|
-
@api_config ||=
|
|
313
|
+
@api_config ||= Config.new.tap do |config|
|
|
269
314
|
config.host = @config[:mq_api_host]
|
|
270
315
|
config.port = @config[:mq_api_port]
|
|
271
316
|
config.username = @config[:mq_username]
|
|
272
317
|
config.password = @config[:mq_password]
|
|
273
318
|
config.ssl = @config[:mq_api_ssl]
|
|
274
319
|
config.protocol = config.ssl ? "https://" : "http://"
|
|
275
|
-
config.sanitized_uri = "#{config.protocol}#{config.username}@#{config.host}:#{config.port}/"
|
|
320
|
+
config.sanitized_uri = "#{config.protocol}#{URI.encode_uri_component(config.username.to_s)}@#{config.host}:#{config.port}/"
|
|
276
321
|
end
|
|
277
322
|
end
|
|
278
323
|
|
|
@@ -282,7 +327,8 @@ module Hutch
|
|
|
282
327
|
{}.tap do |params|
|
|
283
328
|
params[:host] = @config[:mq_host]
|
|
284
329
|
params[:port] = @config[:mq_port]
|
|
285
|
-
params[:vhost] =
|
|
330
|
+
params[:vhost] = vhost
|
|
331
|
+
params[:auth_mechanism] = @config[:mq_auth_mechanism]
|
|
286
332
|
params[:username] = @config[:mq_username]
|
|
287
333
|
params[:password] = @config[:mq_password]
|
|
288
334
|
params[:tls] = @config[:mq_tls]
|
|
@@ -293,13 +339,15 @@ module Hutch
|
|
|
293
339
|
params[:tls_ca_certificates] = @config[:mq_tls_ca_certificates]
|
|
294
340
|
end
|
|
295
341
|
params[:heartbeat] = @config[:heartbeat]
|
|
342
|
+
params[:client_properties] = @config[:mq_client_properties]
|
|
343
|
+
params[:connection_name] = @config[:connection_name]
|
|
296
344
|
params[:connection_timeout] = @config[:connection_timeout]
|
|
297
345
|
params[:read_timeout] = @config[:read_timeout]
|
|
298
346
|
params[:write_timeout] = @config[:write_timeout]
|
|
299
347
|
|
|
300
348
|
|
|
301
|
-
params[:automatically_recover] =
|
|
302
|
-
params[:network_recovery_interval] =
|
|
349
|
+
params[:automatically_recover] = @config[:automatically_recover]
|
|
350
|
+
params[:network_recovery_interval] = @config[:network_recovery_interval]
|
|
303
351
|
|
|
304
352
|
params[:logger] = @config[:client_logger] if @config[:client_logger]
|
|
305
353
|
end
|
|
@@ -313,9 +361,23 @@ module Hutch
|
|
|
313
361
|
@config[:mq_tls] = u.scheme == 'amqps'
|
|
314
362
|
@config[:mq_host] = u.host
|
|
315
363
|
@config[:mq_port] = u.port || default_mq_port
|
|
316
|
-
@config[:mq_vhost] = u
|
|
317
|
-
@config[:mq_username] = u.user
|
|
318
|
-
@config[:mq_password] = u.password
|
|
364
|
+
@config[:mq_vhost] = parse_vhost(u)
|
|
365
|
+
@config[:mq_username] = u.user && URI.decode_uri_component(u.user)
|
|
366
|
+
@config[:mq_password] = u.password && URI.decode_uri_component(u.password)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Resolved here rather than in #connection_params, so that
|
|
370
|
+
# Config[:mq_vhost] holds the name the HTTP API reports too.
|
|
371
|
+
def parse_vhost(uri)
|
|
372
|
+
URI.decode_uri_component(uri.path.sub(%r{\A/}, "")).presence ||
|
|
373
|
+
Hutch::Adapter::DEFAULT_VHOST
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# The vhost Hutch connects to. Config[:mq_vhost] can still be blank when it
|
|
377
|
+
# was set directly rather than through a URI, while the broker and its HTTP
|
|
378
|
+
# API call the default vhost `/`.
|
|
379
|
+
def vhost
|
|
380
|
+
@config[:mq_vhost].presence || Hutch::Adapter::DEFAULT_VHOST
|
|
319
381
|
end
|
|
320
382
|
|
|
321
383
|
def default_mq_port
|
|
@@ -326,7 +388,8 @@ module Hutch
|
|
|
326
388
|
p = connection_params
|
|
327
389
|
scheme = p[:tls] ? "amqps" : "amqp"
|
|
328
390
|
|
|
329
|
-
"#{scheme}://#{p[:username]
|
|
391
|
+
"#{scheme}://#{URI.encode_uri_component(p[:username].to_s)}" \
|
|
392
|
+
"@#{p[:host]}:#{p[:port]}/#{URI.encode_uri_component(p[:vhost].sub(%r{\A/}, ''))}"
|
|
330
393
|
end
|
|
331
394
|
|
|
332
395
|
def with_authentication_error_handler
|
data/lib/hutch/cli.rb
CHANGED
|
@@ -33,6 +33,20 @@ module Hutch
|
|
|
33
33
|
def load_app
|
|
34
34
|
# Try to load a Rails app in the current directory
|
|
35
35
|
load_rails_app('.') if Hutch::Config.autoload_rails
|
|
36
|
+
set_up_code_paths!
|
|
37
|
+
|
|
38
|
+
# Because of the order things are required when we run the Hutch binary
|
|
39
|
+
# in hutch/bin, the sentry-ruby gem gets required **after** the error
|
|
40
|
+
# handlers are set up. Due to this, we never got any Sentry notifications
|
|
41
|
+
# when an error occurred in any of the consumers.
|
|
42
|
+
if defined?(Sentry)
|
|
43
|
+
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def set_up_code_paths!
|
|
36
50
|
Hutch::Config.require_paths.each do |path|
|
|
37
51
|
# See if each path is a Rails app. If so, try to load it.
|
|
38
52
|
next if load_rails_app(path)
|
|
@@ -48,19 +62,9 @@ module Hutch
|
|
|
48
62
|
return false
|
|
49
63
|
ensure
|
|
50
64
|
# Clean up load path
|
|
51
|
-
$LOAD_PATH.
|
|
65
|
+
$LOAD_PATH.delete('.')
|
|
52
66
|
end
|
|
53
67
|
end
|
|
54
|
-
|
|
55
|
-
# Because of the order things are required when we run the Hutch binary
|
|
56
|
-
# in hutch/bin, the Sentry Raven gem gets required **after** the error
|
|
57
|
-
# handlers are set up. Due to this, we never got any Sentry notifications
|
|
58
|
-
# when an error occurred in any of the consumers.
|
|
59
|
-
if defined?(Raven)
|
|
60
|
-
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
true
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
def load_rails_app(path)
|
|
@@ -227,7 +231,7 @@ module Hutch
|
|
|
227
231
|
end
|
|
228
232
|
|
|
229
233
|
def abort_without_file(file, file_description, &block)
|
|
230
|
-
abort_with_message("#{file_description} '#{file}' not found") unless File.
|
|
234
|
+
abort_with_message("#{file_description} '#{file}' not found") unless File.exist?(file)
|
|
231
235
|
|
|
232
236
|
yield
|
|
233
237
|
end
|
data/lib/hutch/config.rb
CHANGED
|
@@ -49,17 +49,23 @@ module Hutch
|
|
|
49
49
|
# RabbitMQ Exchange to use for publishing
|
|
50
50
|
string_setting :mq_exchange, 'hutch'
|
|
51
51
|
|
|
52
|
+
# RabbitMQ Exchange type to use for publishing
|
|
53
|
+
string_setting :mq_exchange_type, 'topic'
|
|
54
|
+
|
|
52
55
|
# RabbitMQ vhost to use
|
|
53
56
|
string_setting :mq_vhost, '/'
|
|
54
57
|
|
|
55
58
|
# RabbitMQ username to use.
|
|
56
59
|
#
|
|
57
|
-
#
|
|
60
|
+
# The <tt>guest</tt> user can only connect from localhost.
|
|
58
61
|
string_setting :mq_username, 'guest'
|
|
59
62
|
|
|
60
63
|
# RabbitMQ password
|
|
61
64
|
string_setting :mq_password, 'guest'
|
|
62
65
|
|
|
66
|
+
# RabbitMQ Auth Mechanism
|
|
67
|
+
string_setting :mq_auth_mechanism, 'PLAIN'
|
|
68
|
+
|
|
63
69
|
# RabbitMQ URI (takes precedence over MQ username, password, host, port and vhost settings)
|
|
64
70
|
string_setting :uri, nil
|
|
65
71
|
|
|
@@ -72,7 +78,7 @@ module Hutch
|
|
|
72
78
|
# RabbitMQ HTTP API port
|
|
73
79
|
number_setting :mq_api_port, 15672
|
|
74
80
|
|
|
75
|
-
# [RabbitMQ heartbeat timeout](
|
|
81
|
+
# [RabbitMQ heartbeat timeout](https://www.rabbitmq.com/docs/heartbeats)
|
|
76
82
|
number_setting :heartbeat, 30
|
|
77
83
|
|
|
78
84
|
# The <tt>basic.qos</tt> prefetch value to use.
|
|
@@ -80,6 +86,9 @@ module Hutch
|
|
|
80
86
|
# Default: `0`, no limit. See Bunny and RabbitMQ documentation.
|
|
81
87
|
number_setting :channel_prefetch, 0
|
|
82
88
|
|
|
89
|
+
# [Client-Provided Connection Name](https://www.rabbitmq.com/docs/connections#client-provided-names)
|
|
90
|
+
string_setting :connection_name, nil
|
|
91
|
+
|
|
83
92
|
# Bunny's socket open timeout
|
|
84
93
|
number_setting :connection_timeout, 11
|
|
85
94
|
|
|
@@ -89,7 +98,13 @@ module Hutch
|
|
|
89
98
|
# Bunny's socket write timeout
|
|
90
99
|
number_setting :write_timeout, 11
|
|
91
100
|
|
|
92
|
-
#
|
|
101
|
+
# Bunny's enable/disable network recovery
|
|
102
|
+
boolean_setting :automatically_recover, true
|
|
103
|
+
|
|
104
|
+
# Bunny's reconnect interval
|
|
105
|
+
number_setting :network_recovery_interval, 1
|
|
106
|
+
|
|
107
|
+
# Timeout (in seconds) for consumer threads to finish before being killed during graceful shutdown
|
|
93
108
|
number_setting :graceful_exit_timeout, 11
|
|
94
109
|
|
|
95
110
|
# Bunny consumer work pool size
|
|
@@ -136,6 +151,9 @@ module Hutch
|
|
|
136
151
|
# Prefix displayed on the consumers tags.
|
|
137
152
|
string_setting :consumer_tag_prefix, 'hutch'
|
|
138
153
|
|
|
154
|
+
# A namespace to help group your queues
|
|
155
|
+
string_setting :namespace, nil
|
|
156
|
+
|
|
139
157
|
string_setting :group, ''
|
|
140
158
|
|
|
141
159
|
# Set of all setting keys
|
|
@@ -156,6 +174,7 @@ module Hutch
|
|
|
156
174
|
# @return [Hash]
|
|
157
175
|
def self.default_config
|
|
158
176
|
@settings_defaults.merge({
|
|
177
|
+
mq_client_properties: {},
|
|
159
178
|
mq_exchange_options: {},
|
|
160
179
|
mq_tls_cert: nil,
|
|
161
180
|
mq_tls_key: nil,
|
|
@@ -184,14 +203,7 @@ module Hutch
|
|
|
184
203
|
env_keys_configured.each_with_object({}) {|attr, result|
|
|
185
204
|
value = ENV[key_for(attr)]
|
|
186
205
|
|
|
187
|
-
|
|
188
|
-
when is_bool(attr) || value == 'false'
|
|
189
|
-
result[attr] = to_bool(value)
|
|
190
|
-
when is_num(attr)
|
|
191
|
-
result[attr] = value.to_i
|
|
192
|
-
else
|
|
193
|
-
result[attr] = value
|
|
194
|
-
end
|
|
206
|
+
result[attr] = type_cast(attr, value)
|
|
195
207
|
}
|
|
196
208
|
end
|
|
197
209
|
|
|
@@ -217,7 +229,12 @@ module Hutch
|
|
|
217
229
|
end
|
|
218
230
|
|
|
219
231
|
def self.to_bool(value)
|
|
220
|
-
|
|
232
|
+
case value
|
|
233
|
+
when nil, false, '', /^(false|f|no|n|0)$/i
|
|
234
|
+
false
|
|
235
|
+
else
|
|
236
|
+
true
|
|
237
|
+
end
|
|
221
238
|
end
|
|
222
239
|
|
|
223
240
|
def self.is_num(attr)
|
|
@@ -226,7 +243,7 @@ module Hutch
|
|
|
226
243
|
|
|
227
244
|
def self.set(attr, value)
|
|
228
245
|
check_attr(attr.to_sym)
|
|
229
|
-
user_config[attr.to_sym] = value
|
|
246
|
+
user_config[attr.to_sym] = type_cast(attr, value)
|
|
230
247
|
end
|
|
231
248
|
|
|
232
249
|
class << self
|
|
@@ -263,6 +280,18 @@ module Hutch
|
|
|
263
280
|
end
|
|
264
281
|
end
|
|
265
282
|
|
|
283
|
+
def self.type_cast(attr, value)
|
|
284
|
+
case
|
|
285
|
+
when is_bool(attr) || value == 'false'
|
|
286
|
+
to_bool(value)
|
|
287
|
+
when is_num(attr)
|
|
288
|
+
value.to_i
|
|
289
|
+
else
|
|
290
|
+
value
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
private_class_method :type_cast
|
|
294
|
+
|
|
266
295
|
def self.define_methods
|
|
267
296
|
@config.keys.each do |key|
|
|
268
297
|
define_singleton_method(key) do
|
data/lib/hutch/consumer.rb
CHANGED
|
@@ -13,11 +13,17 @@ module Hutch
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def reject!
|
|
16
|
-
|
|
16
|
+
@message_rejected = true
|
|
17
|
+
broker.reject(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def requeue!
|
|
20
|
-
|
|
21
|
+
@message_rejected = true
|
|
22
|
+
broker.requeue(delivery_info.delivery_tag, channel: delivery_info.channel)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def message_rejected?
|
|
26
|
+
!!@message_rejected
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def logger
|
|
@@ -29,18 +35,56 @@ module Hutch
|
|
|
29
35
|
# wants to subscribe to.
|
|
30
36
|
def consume(*routing_keys)
|
|
31
37
|
@routing_keys = self.routing_keys.union(routing_keys)
|
|
38
|
+
# these are opt-in
|
|
39
|
+
@queue_mode = nil
|
|
40
|
+
@queue_type = nil
|
|
32
41
|
end
|
|
33
42
|
|
|
43
|
+
attr_reader :queue_mode, :queue_type, :initial_group_size
|
|
44
|
+
|
|
34
45
|
# Explicitly set the queue name
|
|
35
46
|
def queue_name(name)
|
|
36
47
|
@queue_name = name
|
|
37
48
|
end
|
|
38
49
|
|
|
39
|
-
#
|
|
50
|
+
# Opt out of the namespace prefix for this consumer's queue
|
|
51
|
+
def without_namespace
|
|
52
|
+
@without_namespace = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def without_namespace?
|
|
56
|
+
!!@without_namespace
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Explicitly set the queue mode to 'lazy'
|
|
60
|
+
def lazy_queue
|
|
61
|
+
@queue_mode = 'lazy'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Explicitly set the queue type to 'classic'
|
|
65
|
+
def classic_queue
|
|
66
|
+
@queue_type = 'classic'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Explicitly set the queue type to 'quorum'
|
|
70
|
+
# @param [Hash] options the options params related to quorum queue
|
|
71
|
+
# @option options [Integer] :initial_group_size Initial Replication Factor
|
|
72
|
+
def quorum_queue(options = {})
|
|
73
|
+
@queue_type = 'quorum'
|
|
74
|
+
@initial_group_size = options[:initial_group_size]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Configures an optional argument that will be passed when declaring the queue.
|
|
78
|
+
# Prefer using a policy to this DSL: https://www.rabbitmq.com/parameters.html#policies
|
|
40
79
|
def arguments(arguments = {})
|
|
41
80
|
@arguments = arguments
|
|
42
81
|
end
|
|
43
82
|
|
|
83
|
+
# Configures queue options that will be passed when declaring the queue.
|
|
84
|
+
def queue_options(options = {})
|
|
85
|
+
@queue_options = options
|
|
86
|
+
end
|
|
87
|
+
|
|
44
88
|
# Set custom serializer class, override global value
|
|
45
89
|
def serializer(name)
|
|
46
90
|
@serializer = name
|
|
@@ -58,7 +102,22 @@ module Hutch
|
|
|
58
102
|
|
|
59
103
|
# Returns consumer custom arguments.
|
|
60
104
|
def get_arguments
|
|
61
|
-
@arguments || {}
|
|
105
|
+
all_arguments = @arguments || {}
|
|
106
|
+
|
|
107
|
+
all_arguments['x-queue-mode'] = @queue_mode if @queue_mode
|
|
108
|
+
all_arguments['x-queue-type'] = @queue_type if @queue_type
|
|
109
|
+
all_arguments['x-quorum-initial-group-size'] = @initial_group_size if @initial_group_size
|
|
110
|
+
|
|
111
|
+
all_arguments
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def get_options
|
|
115
|
+
default_options = { durable: true }
|
|
116
|
+
|
|
117
|
+
all_options = default_options.merge(@queue_options || {})
|
|
118
|
+
all_options[:arguments] = get_arguments
|
|
119
|
+
|
|
120
|
+
all_options
|
|
62
121
|
end
|
|
63
122
|
|
|
64
123
|
# Accessor for the consumer's routing key.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "hutch/logging"
|
|
2
|
+
require "bugsnag"
|
|
3
|
+
require "hutch/error_handlers/base"
|
|
4
|
+
|
|
5
|
+
module Hutch
|
|
6
|
+
module ErrorHandlers
|
|
7
|
+
class Bugsnag < Base
|
|
8
|
+
def handle(properties, payload, consumer, ex)
|
|
9
|
+
message_id = properties.message_id
|
|
10
|
+
prefix = "message(#{message_id || "-"}):"
|
|
11
|
+
logger.error "#{prefix} Logging event to Bugsnag"
|
|
12
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
13
|
+
|
|
14
|
+
::Bugsnag.notify(ex) do |report|
|
|
15
|
+
report.add_tab(:hutch, {
|
|
16
|
+
payload: payload,
|
|
17
|
+
consumer: consumer
|
|
18
|
+
})
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def handle_setup_exception(ex)
|
|
23
|
+
logger.error "Logging setup exception to Bugsnag"
|
|
24
|
+
logger.error "#{ex.class} - #{ex.message}"
|
|
25
|
+
|
|
26
|
+
::Bugsnag.notify(ex)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
require 'hutch/logging'
|
|
2
|
-
require '
|
|
2
|
+
require 'rollbar'
|
|
3
3
|
require 'hutch/error_handlers/base'
|
|
4
4
|
|
|
5
5
|
module Hutch
|
|
6
6
|
module ErrorHandlers
|
|
7
|
-
class
|
|
8
|
-
|
|
9
|
-
def initialize
|
|
10
|
-
unless ::Opbeat.respond_to?(:report)
|
|
11
|
-
raise "The Opbeat error handler requires Opbeat >= 3.0"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
7
|
+
class Rollbar < Base
|
|
15
8
|
def handle(properties, payload, consumer, ex)
|
|
16
9
|
message_id = properties.message_id
|
|
17
10
|
prefix = "message(#{message_id || '-'}):"
|
|
18
|
-
logger.error "#{prefix} Logging event to
|
|
11
|
+
logger.error "#{prefix} Logging event to Rollbar"
|
|
19
12
|
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
20
|
-
|
|
13
|
+
|
|
14
|
+
::Rollbar.error(ex,
|
|
15
|
+
payload: payload,
|
|
16
|
+
consumer: consumer
|
|
17
|
+
)
|
|
21
18
|
end
|
|
22
19
|
|
|
23
20
|
def handle_setup_exception(ex)
|
|
24
|
-
logger.error "Logging setup exception to
|
|
21
|
+
logger.error "Logging setup exception to Rollbar"
|
|
25
22
|
logger.error "#{ex.class} - #{ex.message}"
|
|
26
|
-
|
|
23
|
+
|
|
24
|
+
::Rollbar.error(ex)
|
|
27
25
|
end
|
|
28
26
|
end
|
|
29
27
|
end
|
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
require 'hutch/logging'
|
|
2
|
-
require '
|
|
2
|
+
require 'sentry-ruby'
|
|
3
3
|
require 'hutch/error_handlers/base'
|
|
4
4
|
|
|
5
5
|
module Hutch
|
|
6
6
|
module ErrorHandlers
|
|
7
7
|
class Sentry < Base
|
|
8
|
-
|
|
9
|
-
def initialize
|
|
10
|
-
unless Raven.respond_to?(:capture_exception)
|
|
11
|
-
raise "The Hutch Sentry error handler requires Raven >= 0.4.0"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
8
|
def handle(properties, payload, consumer, ex)
|
|
16
9
|
message_id = properties.message_id
|
|
17
10
|
prefix = "message(#{message_id || '-'}):"
|
|
18
11
|
logger.error "#{prefix} Logging event to Sentry"
|
|
19
12
|
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
|
20
|
-
|
|
13
|
+
::Sentry.configure_scope do |scope|
|
|
14
|
+
scope.set_context("payload", JSON.parse(payload))
|
|
15
|
+
end
|
|
16
|
+
::Sentry.capture_exception(ex)
|
|
21
17
|
end
|
|
22
18
|
|
|
23
19
|
def handle_setup_exception(ex)
|
|
24
20
|
logger.error "Logging setup exception to Sentry"
|
|
25
21
|
logger.error "#{ex.class} - #{ex.message}"
|
|
26
|
-
|
|
22
|
+
::Sentry.capture_exception(ex)
|
|
27
23
|
end
|
|
28
|
-
|
|
29
24
|
end
|
|
30
25
|
end
|
|
31
26
|
end
|
data/lib/hutch/error_handlers.rb
CHANGED
|
@@ -4,6 +4,7 @@ module Hutch
|
|
|
4
4
|
autoload :Sentry, 'hutch/error_handlers/sentry'
|
|
5
5
|
autoload :Honeybadger, 'hutch/error_handlers/honeybadger'
|
|
6
6
|
autoload :Airbrake, 'hutch/error_handlers/airbrake'
|
|
7
|
-
autoload :
|
|
7
|
+
autoload :Rollbar, 'hutch/error_handlers/rollbar'
|
|
8
|
+
autoload :Bugsnag, 'hutch/error_handlers/bugsnag'
|
|
8
9
|
end
|
|
9
10
|
end
|
data/lib/hutch/publisher.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'json'
|
|
2
2
|
require 'active_support/core_ext/hash/indifferent_access'
|
|
3
3
|
|
|
4
4
|
module Hutch
|
|
@@ -6,11 +6,11 @@ module Hutch
|
|
|
6
6
|
class JSON
|
|
7
7
|
|
|
8
8
|
def self.encode(payload)
|
|
9
|
-
::JSON.
|
|
9
|
+
::JSON.generate(payload)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def self.decode(payload)
|
|
13
|
-
::
|
|
13
|
+
::JSON.parse(payload).with_indifferent_access
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.binary? ; false ; end
|