shivam 0.0.0-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 +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
|
+
|