shivam 0.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +57 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +899 -0
- data/Gemfile +35 -0
- data/Guardfile +14 -0
- data/LICENSE +23 -0
- data/README.md +679 -0
- data/Rakefile +21 -0
- data/bin/ci/before_build.sh +20 -0
- data/bin/ci/before_build_docker.sh +20 -0
- data/bin/ci/install_on_debian.sh +46 -0
- data/bin/hutch +8 -0
- data/examples/consumer.rb +13 -0
- data/examples/producer.rb +10 -0
- data/hutch.gemspec +27 -0
- data/lib/hutch/acknowledgements/base.rb +16 -0
- data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
- data/lib/hutch/adapter.rb +11 -0
- data/lib/hutch/adapters/bunny.rb +37 -0
- data/lib/hutch/adapters/march_hare.rb +41 -0
- data/lib/hutch/broker.rb +384 -0
- data/lib/hutch/cli.rb +246 -0
- data/lib/hutch/config.rb +305 -0
- data/lib/hutch/consumer.rb +125 -0
- data/lib/hutch/error_handlers/airbrake.rb +54 -0
- data/lib/hutch/error_handlers/base.rb +15 -0
- data/lib/hutch/error_handlers/bugsnag.rb +30 -0
- data/lib/hutch/error_handlers/honeybadger.rb +43 -0
- data/lib/hutch/error_handlers/logger.rb +22 -0
- data/lib/hutch/error_handlers/rollbar.rb +28 -0
- data/lib/hutch/error_handlers/sentry.rb +26 -0
- data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
- data/lib/hutch/error_handlers.rb +11 -0
- data/lib/hutch/exceptions.rb +14 -0
- data/lib/hutch/logging.rb +32 -0
- data/lib/hutch/message.rb +31 -0
- data/lib/hutch/publisher.rb +75 -0
- data/lib/hutch/serializers/identity.rb +19 -0
- data/lib/hutch/serializers/json.rb +22 -0
- data/lib/hutch/tracers/datadog.rb +18 -0
- data/lib/hutch/tracers/newrelic.rb +19 -0
- data/lib/hutch/tracers/null_tracer.rb +15 -0
- data/lib/hutch/tracers.rb +7 -0
- data/lib/hutch/version.rb +3 -0
- data/lib/hutch/waiter.rb +104 -0
- data/lib/hutch/worker.rb +145 -0
- data/lib/hutch.rb +69 -0
- data/lib/yard-settings/handler.rb +38 -0
- data/lib/yard-settings/yard-settings.rb +2 -0
- data/spec/hutch/broker_spec.rb +462 -0
- data/spec/hutch/cli_spec.rb +93 -0
- data/spec/hutch/config_spec.rb +259 -0
- data/spec/hutch/consumer_spec.rb +208 -0
- data/spec/hutch/error_handlers/airbrake_spec.rb +49 -0
- data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +58 -0
- data/spec/hutch/error_handlers/logger_spec.rb +28 -0
- data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
- data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +47 -0
- data/spec/hutch/logger_spec.rb +34 -0
- data/spec/hutch/message_spec.rb +38 -0
- data/spec/hutch/serializers/json_spec.rb +17 -0
- data/spec/hutch/tracers/datadog_spec.rb +44 -0
- data/spec/hutch/waiter_spec.rb +51 -0
- data/spec/hutch/worker_spec.rb +184 -0
- data/spec/hutch_spec.rb +87 -0
- data/spec/spec_helper.rb +42 -0
- data/templates/default/class/html/settings.erb +0 -0
- data/templates/default/class/setup.rb +4 -0
- data/templates/default/fulldoc/html/css/hutch.css +13 -0
- data/templates/default/layout/html/setup.rb +7 -0
- data/templates/default/method_details/html/settings.erb +5 -0
- data/templates/default/method_details/setup.rb +4 -0
- data/templates/default/method_details/text/settings.erb +0 -0
- data/templates/default/module/html/settings.erb +40 -0
- data/templates/default/module/setup.rb +4 -0
- metadata +205 -0
data/lib/hutch/cli.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'hutch/logging'
|
4
|
+
require 'hutch/config'
|
5
|
+
require 'hutch/version'
|
6
|
+
require 'hutch/exceptions'
|
7
|
+
|
8
|
+
module Hutch
|
9
|
+
class CLI
|
10
|
+
include Logging
|
11
|
+
|
12
|
+
# Run a Hutch worker with the command line interface.
|
13
|
+
def run(argv = ARGV)
|
14
|
+
Hutch::Config.initialize
|
15
|
+
parse_options(argv)
|
16
|
+
|
17
|
+
daemonise_process
|
18
|
+
|
19
|
+
write_pid if Hutch::Config.pidfile
|
20
|
+
|
21
|
+
Hutch.logger.info "hutch booted with pid #{::Process.pid}"
|
22
|
+
|
23
|
+
if load_app && start_work_loop == :success
|
24
|
+
# If we got here, the worker was shut down nicely
|
25
|
+
Hutch.logger.info 'hutch shut down gracefully'
|
26
|
+
exit 0
|
27
|
+
else
|
28
|
+
Hutch.logger.info 'hutch terminated due to an error'
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_app
|
34
|
+
# Try to load a Rails app in the current directory
|
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 Raven 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?(Raven)
|
43
|
+
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::SentryRaven.new
|
44
|
+
end
|
45
|
+
if defined?(Sentry)
|
46
|
+
Hutch::Config[:error_handlers] << Hutch::ErrorHandlers::Sentry.new
|
47
|
+
end
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_up_code_paths!
|
53
|
+
Hutch::Config.require_paths.each do |path|
|
54
|
+
# See if each path is a Rails app. If so, try to load it.
|
55
|
+
next if load_rails_app(path)
|
56
|
+
|
57
|
+
# Given path is not a Rails app, try requiring it as a file
|
58
|
+
logger.info "requiring '#{path}'"
|
59
|
+
begin
|
60
|
+
# Need to add '.' to load path for relative requires
|
61
|
+
$LOAD_PATH << '.'
|
62
|
+
require path
|
63
|
+
rescue LoadError => e
|
64
|
+
logger.fatal "could not load file '#{path}': #{e}"
|
65
|
+
return false
|
66
|
+
ensure
|
67
|
+
# Clean up load path
|
68
|
+
$LOAD_PATH.delete('.')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_rails_app(path)
|
74
|
+
# path should point to the app's top level directory
|
75
|
+
if File.directory?(path)
|
76
|
+
# Smells like a Rails app if it's got a script/rails or bin/rails file
|
77
|
+
is_rails_app = ['script/rails', 'bin/rails'].any? do |file|
|
78
|
+
File.exist?(File.expand_path(File.join(path, file)))
|
79
|
+
end
|
80
|
+
rails_path = File.expand_path(File.join(path, 'config/environment.rb'))
|
81
|
+
if is_rails_app && File.exist?(rails_path)
|
82
|
+
ENV['RACK_ENV'] ||= ENV['RAILS_ENV'] || 'development'
|
83
|
+
logger.info "found rails project (#{path}), booting app in #{ENV['RACK_ENV']} environment"
|
84
|
+
require rails_path
|
85
|
+
::Rails.application.eager_load!
|
86
|
+
return true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
# Kick off the work loop. This method returns when the worker is shut down
|
93
|
+
# gracefully (with a SIGQUIT, SIGTERM or SIGINT).
|
94
|
+
def start_work_loop
|
95
|
+
Hutch.connect
|
96
|
+
@worker = Hutch::Worker.new(Hutch.broker, Hutch.consumers, Hutch::Config.setup_procs)
|
97
|
+
@worker.run
|
98
|
+
:success
|
99
|
+
rescue ConnectionError, AuthenticationError, WorkerSetupError => ex
|
100
|
+
Hutch::Config[:error_handlers].each do |backend|
|
101
|
+
backend.handle_setup_exception(ex)
|
102
|
+
end
|
103
|
+
logger.fatal ex.message
|
104
|
+
:error
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse_options(args = ARGV)
|
108
|
+
OptionParser.new do |opts|
|
109
|
+
opts.banner = 'usage: hutch [options]'
|
110
|
+
|
111
|
+
opts.on('--mq-host HOST', 'Set the RabbitMQ host') do |host|
|
112
|
+
Hutch::Config.mq_host = host
|
113
|
+
end
|
114
|
+
|
115
|
+
opts.on('--mq-port PORT', 'Set the RabbitMQ port') do |port|
|
116
|
+
Hutch::Config.mq_port = port
|
117
|
+
end
|
118
|
+
|
119
|
+
opts.on("-t", "--[no-]mq-tls", 'Use TLS for the AMQP connection') do |tls|
|
120
|
+
Hutch::Config.mq_tls = tls
|
121
|
+
end
|
122
|
+
|
123
|
+
opts.on('--mq-tls-cert FILE', 'Certificate for TLS client verification') do |file|
|
124
|
+
abort_without_file(file, 'Certificate file') do
|
125
|
+
Hutch::Config.mq_tls_cert = file
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on('--mq-tls-key FILE', 'Private key for TLS client verification') do |file|
|
130
|
+
abort_without_file(file, 'Private key file') do
|
131
|
+
Hutch::Config.mq_tls_key = file
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
opts.on('--mq-exchange EXCHANGE',
|
136
|
+
'Set the RabbitMQ exchange') do |exchange|
|
137
|
+
Hutch::Config.mq_exchange = exchange
|
138
|
+
end
|
139
|
+
|
140
|
+
opts.on('--mq-vhost VHOST', 'Set the RabbitMQ vhost') do |vhost|
|
141
|
+
Hutch::Config.mq_vhost = vhost
|
142
|
+
end
|
143
|
+
|
144
|
+
opts.on('--mq-username USERNAME',
|
145
|
+
'Set the RabbitMQ username') do |username|
|
146
|
+
Hutch::Config.mq_username = username
|
147
|
+
end
|
148
|
+
|
149
|
+
opts.on('--mq-password PASSWORD',
|
150
|
+
'Set the RabbitMQ password') do |password|
|
151
|
+
Hutch::Config.mq_password = password
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.on('--mq-api-host HOST', 'Set the RabbitMQ API host') do |host|
|
155
|
+
Hutch::Config.mq_api_host = host
|
156
|
+
end
|
157
|
+
|
158
|
+
opts.on('--mq-api-port PORT', 'Set the RabbitMQ API port') do |port|
|
159
|
+
Hutch::Config.mq_api_port = port
|
160
|
+
end
|
161
|
+
|
162
|
+
opts.on("-s", "--[no-]mq-api-ssl", 'Use SSL for the RabbitMQ API') do |api_ssl|
|
163
|
+
Hutch::Config.mq_api_ssl = api_ssl
|
164
|
+
end
|
165
|
+
|
166
|
+
opts.on('--config FILE', 'Load Hutch configuration from a file') do |file|
|
167
|
+
begin
|
168
|
+
File.open(file) { |fp| Hutch::Config.load_from_file(fp) }
|
169
|
+
rescue Errno::ENOENT
|
170
|
+
abort_with_message("Config file '#{file}' not found")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
opts.on('--require PATH', 'Require a Rails app or path') do |path|
|
175
|
+
Hutch::Config.require_paths << path
|
176
|
+
end
|
177
|
+
|
178
|
+
opts.on('--[no-]autoload-rails', 'Require the current rails app directory') do |autoload_rails|
|
179
|
+
Hutch::Config.autoload_rails = autoload_rails
|
180
|
+
end
|
181
|
+
|
182
|
+
opts.on('-q', '--quiet', 'Quiet logging') do
|
183
|
+
Hutch::Config.log_level = Logger::WARN
|
184
|
+
end
|
185
|
+
|
186
|
+
opts.on('-v', '--verbose', 'Verbose logging') do
|
187
|
+
Hutch::Config.log_level = Logger::DEBUG
|
188
|
+
end
|
189
|
+
|
190
|
+
opts.on('--namespace NAMESPACE', 'Queue namespace') do |namespace|
|
191
|
+
Hutch::Config.namespace = namespace
|
192
|
+
end
|
193
|
+
|
194
|
+
opts.on('-d', '--daemonise', 'Daemonise') do |daemonise|
|
195
|
+
Hutch::Config.daemonise = daemonise
|
196
|
+
end
|
197
|
+
|
198
|
+
opts.on('--pidfile PIDFILE', 'Pidfile') do |pidfile|
|
199
|
+
Hutch::Config.pidfile = pidfile
|
200
|
+
end
|
201
|
+
|
202
|
+
opts.on('--only-group GROUP', 'Load only consumers in this group') do |group|
|
203
|
+
Hutch::Config.group = group
|
204
|
+
end
|
205
|
+
|
206
|
+
opts.on('--version', 'Print the version and exit') do
|
207
|
+
puts "hutch v#{VERSION}"
|
208
|
+
exit 0
|
209
|
+
end
|
210
|
+
|
211
|
+
opts.on('-h', '--help', 'Show this message and exit') do
|
212
|
+
puts opts
|
213
|
+
exit 0
|
214
|
+
end
|
215
|
+
end.parse!(args)
|
216
|
+
end
|
217
|
+
|
218
|
+
def write_pid
|
219
|
+
pidfile = File.expand_path(Hutch::Config.pidfile)
|
220
|
+
Hutch.logger.info "writing pid in #{pidfile}"
|
221
|
+
File.open(pidfile, 'w') { |f| f.puts ::Process.pid }
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def daemonise_process
|
227
|
+
return unless Hutch::Config.daemonise
|
228
|
+
if defined?(JRUBY_VERSION)
|
229
|
+
Hutch.logger.warn "JRuby ignores the --daemonise option"
|
230
|
+
return
|
231
|
+
end
|
232
|
+
|
233
|
+
::Process.daemon(true)
|
234
|
+
end
|
235
|
+
|
236
|
+
def abort_without_file(file, file_description, &block)
|
237
|
+
abort_with_message("#{file_description} '#{file}' not found") unless File.exist?(file)
|
238
|
+
|
239
|
+
yield
|
240
|
+
end
|
241
|
+
|
242
|
+
def abort_with_message(message)
|
243
|
+
abort message
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
data/lib/hutch/config.rb
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'hutch/error_handlers/logger'
|
2
|
+
require 'hutch/tracers'
|
3
|
+
require 'hutch/serializers/json'
|
4
|
+
require 'erb'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
module Hutch
|
8
|
+
class UnknownAttributeError < StandardError; end
|
9
|
+
|
10
|
+
# Configuration settings, available everywhere
|
11
|
+
#
|
12
|
+
# There are defaults, which can be overridden by ENV variables prefixed by
|
13
|
+
# <tt>HUTCH_</tt>, and each of these can be overridden using the {.set}
|
14
|
+
# method.
|
15
|
+
#
|
16
|
+
# @example Configuring on the command-line
|
17
|
+
# HUTCH_PUBLISHER_CONFIRMS=false hutch
|
18
|
+
module Config
|
19
|
+
require 'yaml'
|
20
|
+
@string_keys = Set.new
|
21
|
+
@number_keys = Set.new
|
22
|
+
@boolean_keys = Set.new
|
23
|
+
@settings_defaults = {}
|
24
|
+
|
25
|
+
# Define a String user setting
|
26
|
+
# @!visibility private
|
27
|
+
def self.string_setting(name, default_value)
|
28
|
+
@string_keys << name
|
29
|
+
@settings_defaults[name] = default_value
|
30
|
+
end
|
31
|
+
|
32
|
+
# Define a Number user setting
|
33
|
+
# @!visibility private
|
34
|
+
def self.number_setting(name, default_value)
|
35
|
+
@number_keys << name
|
36
|
+
@settings_defaults[name] = default_value
|
37
|
+
end
|
38
|
+
|
39
|
+
# Define a Boolean user setting
|
40
|
+
# @!visibility private
|
41
|
+
def self.boolean_setting(name, default_value)
|
42
|
+
@boolean_keys << name
|
43
|
+
@settings_defaults[name] = default_value
|
44
|
+
end
|
45
|
+
|
46
|
+
# RabbitMQ hostname
|
47
|
+
string_setting :mq_host, '127.0.0.1'
|
48
|
+
|
49
|
+
# RabbitMQ Exchange to use for publishing
|
50
|
+
string_setting :mq_exchange, 'hutch'
|
51
|
+
|
52
|
+
# RabbitMQ Exchange type to use for publishing
|
53
|
+
string_setting :mq_exchange_type, 'topic'
|
54
|
+
|
55
|
+
# RabbitMQ vhost to use
|
56
|
+
string_setting :mq_vhost, '/'
|
57
|
+
|
58
|
+
# RabbitMQ username to use.
|
59
|
+
#
|
60
|
+
# As of RabbitMQ 3.3.0, <tt>guest</tt> can only can connect from localhost.
|
61
|
+
string_setting :mq_username, 'guest'
|
62
|
+
|
63
|
+
# RabbitMQ password
|
64
|
+
string_setting :mq_password, 'guest'
|
65
|
+
|
66
|
+
# RabbitMQ Auth Mechanism
|
67
|
+
string_setting :mq_auth_mechanism, 'PLAIN'
|
68
|
+
|
69
|
+
# RabbitMQ URI (takes precedence over MQ username, password, host, port and vhost settings)
|
70
|
+
string_setting :uri, nil
|
71
|
+
|
72
|
+
# RabbitMQ HTTP API hostname
|
73
|
+
string_setting :mq_api_host, '127.0.0.1'
|
74
|
+
|
75
|
+
# RabbitMQ port
|
76
|
+
# RabbitMQ port
|
77
|
+
number_setting :mq_port, 5672
|
78
|
+
|
79
|
+
# RabbitMQ HTTP API port
|
80
|
+
number_setting :mq_api_port, 15672
|
81
|
+
|
82
|
+
# [RabbitMQ heartbeat timeout](http://rabbitmq.com/heartbeats.html)
|
83
|
+
number_setting :heartbeat, 30
|
84
|
+
|
85
|
+
# The <tt>basic.qos</tt> prefetch value to use.
|
86
|
+
#
|
87
|
+
# Default: `0`, no limit. See Bunny and RabbitMQ documentation.
|
88
|
+
number_setting :channel_prefetch, 0
|
89
|
+
|
90
|
+
# Bunny's socket open timeout
|
91
|
+
number_setting :connection_timeout, 11
|
92
|
+
|
93
|
+
# Bunny's socket read timeout
|
94
|
+
number_setting :read_timeout, 11
|
95
|
+
|
96
|
+
# Bunny's socket write timeout
|
97
|
+
number_setting :write_timeout, 11
|
98
|
+
|
99
|
+
# Bunny's enable/disable network recovery
|
100
|
+
boolean_setting :automatically_recover, true
|
101
|
+
|
102
|
+
# Bunny's reconnect interval
|
103
|
+
number_setting :network_recovery_interval, 1
|
104
|
+
|
105
|
+
# FIXME: DOCUMENT THIS
|
106
|
+
number_setting :graceful_exit_timeout, 11
|
107
|
+
|
108
|
+
# Bunny consumer work pool size
|
109
|
+
number_setting :consumer_pool_size, 1
|
110
|
+
|
111
|
+
# Should TLS be used?
|
112
|
+
boolean_setting :mq_tls, false
|
113
|
+
|
114
|
+
# Should SSL certificate be verified?
|
115
|
+
boolean_setting :mq_verify_peer, true
|
116
|
+
|
117
|
+
# Should SSL be used for the RabbitMQ API?
|
118
|
+
boolean_setting :mq_api_ssl, false
|
119
|
+
|
120
|
+
# Should the current Rails app directory be required?
|
121
|
+
boolean_setting :autoload_rails, true
|
122
|
+
|
123
|
+
# Should the Hutch runner process daemonise?
|
124
|
+
#
|
125
|
+
# The option is ignored on JRuby.
|
126
|
+
boolean_setting :daemonise, false
|
127
|
+
|
128
|
+
# Should RabbitMQ publisher confirms be enabled?
|
129
|
+
#
|
130
|
+
# Leaves it up to the app how they are tracked
|
131
|
+
# (e.g. using Hutch::Broker#confirm_select callback or Hutch::Broker#wait_for_confirms)
|
132
|
+
boolean_setting :publisher_confirms, false
|
133
|
+
|
134
|
+
# Enables publisher confirms, forces Hutch::Broker#wait_for_confirms for
|
135
|
+
# every publish.
|
136
|
+
#
|
137
|
+
# **This is the safest option which also offers the
|
138
|
+
# lowest throughput**.
|
139
|
+
boolean_setting :force_publisher_confirms, false
|
140
|
+
|
141
|
+
# Should the RabbitMQ HTTP API be used?
|
142
|
+
boolean_setting :enable_http_api_use, true
|
143
|
+
|
144
|
+
# Should Bunny's consumer work pool threads abort on exception.
|
145
|
+
#
|
146
|
+
# The option is ignored on JRuby.
|
147
|
+
boolean_setting :consumer_pool_abort_on_exception, false
|
148
|
+
|
149
|
+
# Prefix displayed on the consumers tags.
|
150
|
+
string_setting :consumer_tag_prefix, 'hutch'
|
151
|
+
|
152
|
+
# A namespace to help group your queues
|
153
|
+
string_setting :namespace, nil
|
154
|
+
|
155
|
+
string_setting :group, ''
|
156
|
+
|
157
|
+
# Set of all setting keys
|
158
|
+
ALL_KEYS = @boolean_keys + @number_keys + @string_keys
|
159
|
+
|
160
|
+
def self.initialize(params = {})
|
161
|
+
unless @config
|
162
|
+
@config = default_config
|
163
|
+
define_methods
|
164
|
+
@config.merge!(env_based_config)
|
165
|
+
end
|
166
|
+
@config.merge!(params)
|
167
|
+
@config
|
168
|
+
end
|
169
|
+
|
170
|
+
# Default settings
|
171
|
+
#
|
172
|
+
# @return [Hash]
|
173
|
+
def self.default_config
|
174
|
+
@settings_defaults.merge({
|
175
|
+
mq_exchange_options: {},
|
176
|
+
mq_tls_cert: nil,
|
177
|
+
mq_tls_key: nil,
|
178
|
+
mq_tls_ca_certificates: nil,
|
179
|
+
uri: nil,
|
180
|
+
log_level: Logger::INFO,
|
181
|
+
client_logger: nil,
|
182
|
+
require_paths: [],
|
183
|
+
error_handlers: [Hutch::ErrorHandlers::Logger.new],
|
184
|
+
# note that this is not a list, it is a chain of responsibility
|
185
|
+
# that will fall back to "nack unconditionally"
|
186
|
+
error_acknowledgements: [],
|
187
|
+
setup_procs: [],
|
188
|
+
consumer_groups: {},
|
189
|
+
tracer: Hutch::Tracers::NullTracer,
|
190
|
+
namespace: nil,
|
191
|
+
pidfile: nil,
|
192
|
+
serializer: Hutch::Serializers::JSON
|
193
|
+
})
|
194
|
+
end
|
195
|
+
|
196
|
+
# Override defaults with ENV variables which begin with <tt>HUTCH_</tt>
|
197
|
+
#
|
198
|
+
# @return [Hash]
|
199
|
+
def self.env_based_config
|
200
|
+
env_keys_configured.each_with_object({}) {|attr, result|
|
201
|
+
value = ENV[key_for(attr)]
|
202
|
+
|
203
|
+
result[attr] = type_cast(attr, value)
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [Array<Symbol>]
|
208
|
+
def self.env_keys_configured
|
209
|
+
ALL_KEYS.each {|attr| check_attr(attr) }
|
210
|
+
|
211
|
+
ALL_KEYS.select { |attr| ENV.key?(key_for(attr)) }
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.get(attr)
|
215
|
+
check_attr(attr.to_sym)
|
216
|
+
user_config[attr.to_sym]
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.key_for(attr)
|
220
|
+
key = attr.to_s.gsub('.', '__').upcase
|
221
|
+
"HUTCH_#{key}"
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.is_bool(attr)
|
225
|
+
@boolean_keys.include?(attr)
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.to_bool(value)
|
229
|
+
case value
|
230
|
+
when nil, false, '', /^(false|f|no|n|0)$/i
|
231
|
+
false
|
232
|
+
else
|
233
|
+
true
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.is_num(attr)
|
238
|
+
@number_keys.include?(attr)
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.set(attr, value)
|
242
|
+
check_attr(attr.to_sym)
|
243
|
+
user_config[attr.to_sym] = type_cast(attr, value)
|
244
|
+
end
|
245
|
+
|
246
|
+
class << self
|
247
|
+
alias_method :[], :get
|
248
|
+
alias_method :[]=, :set
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.check_attr(attr)
|
252
|
+
unless user_config.key?(attr)
|
253
|
+
raise UnknownAttributeError, "#{attr.inspect} is not a valid config attribute"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.user_config
|
258
|
+
@config ||= initialize
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.to_hash
|
262
|
+
user_config
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.load_from_file(file)
|
266
|
+
YAML.load(ERB.new(File.read(file)).result).each do |attr, value|
|
267
|
+
Hutch::Config.send("#{attr}=", convert_value(attr, value))
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.convert_value(attr, value)
|
272
|
+
case attr
|
273
|
+
when 'tracer'
|
274
|
+
Kernel.const_get(value)
|
275
|
+
else
|
276
|
+
value
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.type_cast(attr, value)
|
281
|
+
case
|
282
|
+
when is_bool(attr) || value == 'false'
|
283
|
+
to_bool(value)
|
284
|
+
when is_num(attr)
|
285
|
+
value.to_i
|
286
|
+
else
|
287
|
+
value
|
288
|
+
end
|
289
|
+
end
|
290
|
+
private_class_method :type_cast
|
291
|
+
|
292
|
+
def self.define_methods
|
293
|
+
@config.keys.each do |key|
|
294
|
+
define_singleton_method(key) do
|
295
|
+
get(key)
|
296
|
+
end
|
297
|
+
|
298
|
+
define_singleton_method("#{key}=") do |val|
|
299
|
+
set(key, val)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
Hutch::Config.initialize
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Hutch
|
4
|
+
# Include this module in a class to register it as a consumer. Consumers
|
5
|
+
# gain a class method called `consume`, which should be used to register
|
6
|
+
# the routing keys a consumer is interested in.
|
7
|
+
module Consumer
|
8
|
+
attr_accessor :broker, :delivery_info
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
Hutch.register_consumer(base)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reject!
|
16
|
+
@message_rejected = true
|
17
|
+
broker.reject(delivery_info.delivery_tag)
|
18
|
+
end
|
19
|
+
|
20
|
+
def requeue!
|
21
|
+
@message_rejected = true
|
22
|
+
broker.requeue(delivery_info.delivery_tag)
|
23
|
+
end
|
24
|
+
|
25
|
+
def message_rejected?
|
26
|
+
!!@message_rejected
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
Hutch::Logging.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
# Add one or more routing keys to the set of routing keys the consumer
|
35
|
+
# wants to subscribe to.
|
36
|
+
def consume(*routing_keys)
|
37
|
+
@routing_keys = self.routing_keys.union(routing_keys)
|
38
|
+
# these are opt-in
|
39
|
+
@queue_mode = nil
|
40
|
+
@queue_type = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :queue_mode, :queue_type, :initial_group_size
|
44
|
+
|
45
|
+
# Explicitly set the queue name
|
46
|
+
def queue_name(name)
|
47
|
+
@queue_name = name
|
48
|
+
end
|
49
|
+
|
50
|
+
# Explicitly set the queue mode to 'lazy'
|
51
|
+
def lazy_queue
|
52
|
+
@queue_mode = 'lazy'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Explicitly set the queue type to 'classic'
|
56
|
+
def classic_queue
|
57
|
+
@queue_type = 'classic'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Explicitly set the queue type to 'quorum'
|
61
|
+
# @param [Hash] options the options params related to quorum queue
|
62
|
+
# @option options [Integer] :initial_group_size Initial Replication Factor
|
63
|
+
def quorum_queue(options = {})
|
64
|
+
@queue_type = 'quorum'
|
65
|
+
@initial_group_size = options[:initial_group_size]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Configures an optional argument that will be passed when declaring the queue.
|
69
|
+
# Prefer using a policy to this DSL: https://www.rabbitmq.com/parameters.html#policies
|
70
|
+
def arguments(arguments = {})
|
71
|
+
@arguments = arguments
|
72
|
+
end
|
73
|
+
|
74
|
+
# Congfiures queue options that will be passed when declaring the queue.
|
75
|
+
def queue_options(options = {})
|
76
|
+
@queue_options = options
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set custom serializer class, override global value
|
80
|
+
def serializer(name)
|
81
|
+
@serializer = name
|
82
|
+
end
|
83
|
+
|
84
|
+
# The RabbitMQ queue name for the consumer. This is derived from the
|
85
|
+
# fully-qualified class name. Module separators are replaced with single
|
86
|
+
# colons, camelcased class names are converted to snake case.
|
87
|
+
def get_queue_name
|
88
|
+
return @queue_name unless @queue_name.nil?
|
89
|
+
queue_name = self.name.gsub(/::/, ':')
|
90
|
+
queue_name.gsub!(/([^A-Z:])([A-Z])/) { "#{$1}_#{$2}" }
|
91
|
+
queue_name.downcase
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns consumer custom arguments.
|
95
|
+
def get_arguments
|
96
|
+
all_arguments = @arguments || {}
|
97
|
+
|
98
|
+
all_arguments['x-queue-mode'] = @queue_mode if @queue_mode
|
99
|
+
all_arguments['x-queue-type'] = @queue_type if @queue_type
|
100
|
+
all_arguments['x-quorum-initial-group-size'] = @initial_group_size if @initial_group_size
|
101
|
+
|
102
|
+
all_arguments
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_options
|
106
|
+
default_options = { durable: true }
|
107
|
+
|
108
|
+
all_options = default_options.merge(@queue_options || {})
|
109
|
+
all_options[:arguments] = get_arguments
|
110
|
+
|
111
|
+
all_options
|
112
|
+
end
|
113
|
+
|
114
|
+
# Accessor for the consumer's routing key.
|
115
|
+
def routing_keys
|
116
|
+
@routing_keys ||= Set.new
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_serializer
|
120
|
+
@serializer
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|