hutch 0.21.0-java → 0.25.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 +5 -5
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.travis.yml +11 -12
- data/.yardopts +5 -0
- data/CHANGELOG.md +118 -1
- data/Gemfile +15 -4
- data/Guardfile +13 -4
- data/README.md +274 -24
- data/Rakefile +8 -1
- data/hutch.gemspec +6 -7
- data/lib/hutch.rb +11 -8
- data/lib/hutch/adapters/march_hare.rb +1 -1
- data/lib/hutch/broker.rb +113 -110
- data/lib/hutch/cli.rb +42 -11
- data/lib/hutch/config.rb +209 -59
- data/lib/hutch/error_handlers.rb +1 -0
- data/lib/hutch/error_handlers/airbrake.rb +44 -16
- data/lib/hutch/error_handlers/base.rb +15 -0
- data/lib/hutch/error_handlers/honeybadger.rb +33 -18
- data/lib/hutch/error_handlers/logger.rb +12 -6
- data/lib/hutch/error_handlers/opbeat.rb +30 -0
- data/lib/hutch/error_handlers/sentry.rb +14 -6
- data/lib/hutch/logging.rb +5 -5
- data/lib/hutch/publisher.rb +75 -0
- data/lib/hutch/tracers.rb +1 -0
- data/lib/hutch/tracers/opbeat.rb +37 -0
- data/lib/hutch/version.rb +1 -1
- data/lib/hutch/waiter.rb +104 -0
- data/lib/hutch/worker.rb +50 -66
- data/lib/yard-settings/handler.rb +38 -0
- data/lib/yard-settings/yard-settings.rb +2 -0
- data/spec/hutch/broker_spec.rb +162 -77
- data/spec/hutch/cli_spec.rb +16 -3
- data/spec/hutch/config_spec.rb +83 -22
- data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
- data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
- data/spec/hutch/error_handlers/logger_spec.rb +14 -1
- data/spec/hutch/error_handlers/opbeat_spec.rb +37 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +18 -1
- data/spec/hutch/logger_spec.rb +12 -6
- data/spec/hutch/waiter_spec.rb +51 -0
- data/spec/hutch/worker_spec.rb +33 -4
- data/spec/spec_helper.rb +7 -5
- data/spec/tracers/opbeat_spec.rb +44 -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 +41 -38
@@ -1,25 +1,53 @@
|
|
1
1
|
require 'hutch/logging'
|
2
2
|
require 'airbrake'
|
3
|
+
require 'hutch/error_handlers/base'
|
3
4
|
|
4
5
|
module Hutch
|
5
6
|
module ErrorHandlers
|
6
|
-
class Airbrake
|
7
|
-
include Logging
|
7
|
+
class Airbrake < Base
|
8
8
|
|
9
|
-
def handle(
|
10
|
-
|
11
|
-
|
12
|
-
logger.error
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
:
|
19
|
-
:
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
def handle(properties, payload, consumer, ex)
|
10
|
+
message_id = properties.message_id
|
11
|
+
prefix = "message(#{message_id || '-'}):"
|
12
|
+
logger.error "#{prefix} Logging event to Airbrake"
|
13
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
14
|
+
|
15
|
+
if ::Airbrake.respond_to?(:notify_or_ignore)
|
16
|
+
::Airbrake.notify_or_ignore(ex, {
|
17
|
+
error_class: ex.class.name,
|
18
|
+
error_message: "#{ ex.class.name }: #{ ex.message }",
|
19
|
+
backtrace: ex.backtrace,
|
20
|
+
parameters: {
|
21
|
+
payload: payload,
|
22
|
+
consumer: consumer,
|
23
|
+
},
|
24
|
+
cgi_data: ENV.to_hash,
|
25
|
+
})
|
26
|
+
else
|
27
|
+
::Airbrake.notify(ex, {
|
28
|
+
payload: payload,
|
29
|
+
consumer: consumer,
|
30
|
+
cgi_data: ENV.to_hash,
|
31
|
+
})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_setup_exception(ex)
|
36
|
+
logger.error "Logging setup exception to Airbrake"
|
37
|
+
logger.error "#{ex.class} - #{ex.message}"
|
38
|
+
|
39
|
+
if ::Airbrake.respond_to?(:notify_or_ignore)
|
40
|
+
::Airbrake.notify_or_ignore(ex, {
|
41
|
+
error_class: ex.class.name,
|
42
|
+
error_message: "#{ ex.class.name }: #{ ex.message }",
|
43
|
+
backtrace: ex.backtrace,
|
44
|
+
cgi_data: ENV.to_hash,
|
45
|
+
})
|
46
|
+
else
|
47
|
+
::Airbrake.notify(ex, {
|
48
|
+
cgi_data: ENV.to_hash,
|
49
|
+
})
|
50
|
+
end
|
23
51
|
end
|
24
52
|
end
|
25
53
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Hutch
|
2
|
+
module ErrorHandlers
|
3
|
+
class Base
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
def handle(properties, payload, consumer, ex)
|
7
|
+
raise NotImplementedError.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_setup_exception(ex)
|
11
|
+
raise NotImplementedError.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,27 +1,42 @@
|
|
1
1
|
require 'hutch/logging'
|
2
2
|
require 'honeybadger'
|
3
|
+
require 'hutch/error_handlers/base'
|
3
4
|
|
4
5
|
module Hutch
|
5
6
|
module ErrorHandlers
|
6
|
-
|
7
|
-
|
7
|
+
# Error handler for the Honeybadger.io service
|
8
|
+
class Honeybadger < Base
|
8
9
|
|
9
|
-
def handle(
|
10
|
-
|
11
|
-
|
12
|
-
logger.error
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
10
|
+
def handle(properties, payload, consumer, ex)
|
11
|
+
message_id = properties.message_id
|
12
|
+
prefix = "message(#{message_id || '-'}):"
|
13
|
+
logger.error "#{prefix} Logging event to Honeybadger"
|
14
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
15
|
+
notify_honeybadger(error_class: ex.class.name,
|
16
|
+
error_message: "#{ex.class.name}: #{ex.message}",
|
17
|
+
backtrace: ex.backtrace,
|
18
|
+
context: { message_id: message_id,
|
19
|
+
consumer: consumer },
|
20
|
+
parameters: { payload: payload })
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_setup_exception(ex)
|
24
|
+
logger.error "Logging setup exception to Honeybadger"
|
25
|
+
logger.error "#{ex.class} - #{ex.message}"
|
26
|
+
notify_honeybadger(error_class: ex.class.name,
|
27
|
+
error_message: "#{ex.class.name}: #{ex.message}",
|
28
|
+
backtrace: ex.backtrace)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Wrap API to support 3.0.0+
|
32
|
+
#
|
33
|
+
# @see https://github.com/honeybadger-io/honeybadger-ruby/blob/master/CHANGELOG.md#300---2017-02-06
|
34
|
+
def notify_honeybadger(message)
|
35
|
+
if ::Honeybadger.respond_to?(:notify_or_ignore)
|
36
|
+
::Honeybadger.notify_or_ignore(message)
|
37
|
+
else
|
38
|
+
::Honeybadger.notify(message)
|
39
|
+
end
|
25
40
|
end
|
26
41
|
end
|
27
42
|
end
|
@@ -1,14 +1,20 @@
|
|
1
1
|
require 'hutch/logging'
|
2
|
+
require 'hutch/error_handlers/base'
|
2
3
|
|
3
4
|
module Hutch
|
4
5
|
module ErrorHandlers
|
5
|
-
class Logger
|
6
|
-
include Logging
|
6
|
+
class Logger < ErrorHandlers::Base
|
7
7
|
|
8
|
-
def handle(
|
9
|
-
|
10
|
-
|
11
|
-
logger.error
|
8
|
+
def handle(properties, payload, consumer, ex)
|
9
|
+
message_id = properties.message_id
|
10
|
+
prefix = "message(#{message_id || '-'}):"
|
11
|
+
logger.error "#{prefix} error in consumer '#{consumer}'"
|
12
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
13
|
+
logger.error (['backtrace:'] + ex.backtrace).join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle_setup_exception(ex)
|
17
|
+
logger.error "#{ex.class} - #{ex.message}"
|
12
18
|
logger.error (['backtrace:'] + ex.backtrace).join("\n")
|
13
19
|
end
|
14
20
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'hutch/logging'
|
2
|
+
require 'opbeat'
|
3
|
+
require 'hutch/error_handlers/base'
|
4
|
+
|
5
|
+
module Hutch
|
6
|
+
module ErrorHandlers
|
7
|
+
class Opbeat < Base
|
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
|
+
|
15
|
+
def handle(properties, payload, consumer, ex)
|
16
|
+
message_id = properties.message_id
|
17
|
+
prefix = "message(#{message_id || '-'}):"
|
18
|
+
logger.error "#{prefix} Logging event to Opbeat"
|
19
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
20
|
+
::Opbeat.report(ex, extra: { payload: payload })
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_setup_exception(ex)
|
24
|
+
logger.error "Logging setup exception to Opbeat"
|
25
|
+
logger.error "#{ex.class} - #{ex.message}"
|
26
|
+
::Opbeat.report(ex)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'hutch/logging'
|
2
2
|
require 'raven'
|
3
|
+
require 'hutch/error_handlers/base'
|
3
4
|
|
4
5
|
module Hutch
|
5
6
|
module ErrorHandlers
|
6
|
-
class Sentry
|
7
|
-
include Logging
|
7
|
+
class Sentry < Base
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
unless Raven.respond_to?(:capture_exception)
|
@@ -12,12 +12,20 @@ module Hutch
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
def handle(
|
16
|
-
|
17
|
-
|
18
|
-
logger.error
|
15
|
+
def handle(properties, payload, consumer, ex)
|
16
|
+
message_id = properties.message_id
|
17
|
+
prefix = "message(#{message_id || '-'}):"
|
18
|
+
logger.error "#{prefix} Logging event to Sentry"
|
19
|
+
logger.error "#{prefix} #{ex.class} - #{ex.message}"
|
20
|
+
Raven.capture_exception(ex, extra: { payload: payload })
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_setup_exception(ex)
|
24
|
+
logger.error "Logging setup exception to Sentry"
|
25
|
+
logger.error "#{ex.class} - #{ex.message}"
|
19
26
|
Raven.capture_exception(ex)
|
20
27
|
end
|
28
|
+
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
data/lib/hutch/logging.rb
CHANGED
@@ -9,12 +9,12 @@ module Hutch
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.setup_logger
|
12
|
+
def self.setup_logger
|
13
13
|
require 'hutch/config'
|
14
|
-
@logger = Logger.new(
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
@logger = Logger.new($stdout).tap do |l|
|
15
|
+
l.level = Hutch::Config.log_level
|
16
|
+
l.formatter = HutchFormatter.new
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.logger
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'hutch/logging'
|
3
|
+
require 'hutch/exceptions'
|
4
|
+
|
5
|
+
module Hutch
|
6
|
+
class Publisher
|
7
|
+
include Logging
|
8
|
+
attr_reader :connection, :channel, :exchange, :config
|
9
|
+
|
10
|
+
def initialize(connection, channel, exchange, config = Hutch::Config)
|
11
|
+
@connection = connection
|
12
|
+
@channel = channel
|
13
|
+
@exchange = exchange
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
def publish(routing_key, message, properties = {}, options = {})
|
18
|
+
ensure_connection!(routing_key, message)
|
19
|
+
|
20
|
+
serializer = options[:serializer] || config[:serializer]
|
21
|
+
|
22
|
+
non_overridable_properties = {
|
23
|
+
routing_key: routing_key,
|
24
|
+
timestamp: connection.current_timestamp,
|
25
|
+
content_type: serializer.content_type,
|
26
|
+
}
|
27
|
+
properties[:message_id] ||= generate_id
|
28
|
+
|
29
|
+
payload = serializer.encode(message)
|
30
|
+
|
31
|
+
log_publication(serializer, payload, routing_key)
|
32
|
+
|
33
|
+
response = exchange.publish(payload, {persistent: true}.
|
34
|
+
merge(properties).
|
35
|
+
merge(global_properties).
|
36
|
+
merge(non_overridable_properties))
|
37
|
+
|
38
|
+
channel.wait_for_confirms if config[:force_publisher_confirms]
|
39
|
+
response
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def log_publication(serializer, payload, routing_key)
|
45
|
+
logger.info {
|
46
|
+
spec =
|
47
|
+
if serializer.binary?
|
48
|
+
"#{payload.bytesize} bytes message"
|
49
|
+
else
|
50
|
+
"message '#{payload}'"
|
51
|
+
end
|
52
|
+
"publishing #{spec} to #{routing_key}"
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def raise_publish_error(reason, routing_key, message)
|
57
|
+
msg = "unable to publish - #{reason}. Message: #{JSON.dump(message)}, Routing key: #{routing_key}."
|
58
|
+
logger.error(msg)
|
59
|
+
raise PublishError, msg
|
60
|
+
end
|
61
|
+
|
62
|
+
def ensure_connection!(routing_key, message)
|
63
|
+
raise_publish_error('no connection to broker', routing_key, message) unless connection
|
64
|
+
raise_publish_error('connection is closed', routing_key, message) unless connection.open?
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_id
|
68
|
+
SecureRandom.uuid
|
69
|
+
end
|
70
|
+
|
71
|
+
def global_properties
|
72
|
+
Hutch.global_properties.respond_to?(:call) ? Hutch.global_properties.call : Hutch.global_properties
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/hutch/tracers.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'opbeat'
|
2
|
+
|
3
|
+
module Hutch
|
4
|
+
module Tracers
|
5
|
+
# Tracer for Opbeat, which traces each message processed.
|
6
|
+
class Opbeat
|
7
|
+
KIND = 'messaging.hutch'.freeze
|
8
|
+
|
9
|
+
# @param klass [Consumer] Consumer instance (!)
|
10
|
+
def initialize(klass)
|
11
|
+
@klass = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param message [Message]
|
15
|
+
def handle(message)
|
16
|
+
::Opbeat.transaction(sig, KIND, extra: extra_from(message)) do
|
17
|
+
@klass.process(message)
|
18
|
+
end.done(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def sig
|
24
|
+
@klass.class.name
|
25
|
+
end
|
26
|
+
|
27
|
+
def extra_from(message)
|
28
|
+
{
|
29
|
+
body: message.body.to_s,
|
30
|
+
message_id: message.message_id,
|
31
|
+
timestamp: message.timestamp,
|
32
|
+
routing_key: message.routing_key
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/hutch/version.rb
CHANGED
data/lib/hutch/waiter.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'hutch/logging'
|
2
|
+
|
3
|
+
module Hutch
|
4
|
+
# Signal-handling class.
|
5
|
+
#
|
6
|
+
# Currently, the signal USR2 performs a thread dump,
|
7
|
+
# while QUIT, TERM and INT all perform a graceful shutdown.
|
8
|
+
class Waiter
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
class ContinueProcessingSignals < RuntimeError
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.supported_signals_of(list)
|
15
|
+
list.keep_if { |s| Signal.list.keys.include?(s) }.tap do |result|
|
16
|
+
result.delete('QUIT') if defined?(JRUBY_VERSION)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
SHUTDOWN_SIGNALS = supported_signals_of(%w(QUIT TERM INT)).freeze
|
21
|
+
# We have chosen a JRuby-supported signal
|
22
|
+
USER_SIGNALS = supported_signals_of(%w(USR2)).freeze
|
23
|
+
REGISTERED_SIGNALS = (SHUTDOWN_SIGNALS + USER_SIGNALS).freeze
|
24
|
+
|
25
|
+
def self.wait_until_signaled
|
26
|
+
new.wait_until_signaled
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait_until_signaled
|
30
|
+
self.sig_read, self.sig_write = IO.pipe
|
31
|
+
|
32
|
+
register_signal_handlers
|
33
|
+
|
34
|
+
begin
|
35
|
+
wait_for_signal
|
36
|
+
|
37
|
+
sig = sig_read.gets.strip
|
38
|
+
handle_signal(sig)
|
39
|
+
rescue ContinueProcessingSignals
|
40
|
+
retry
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_signal(sig)
|
45
|
+
raise ContinueProcessingSignals unless REGISTERED_SIGNALS.include?(sig)
|
46
|
+
if user_signal?(sig)
|
47
|
+
handle_user_signal(sig)
|
48
|
+
else
|
49
|
+
handle_shutdown_signal(sig)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @raise ContinueProcessingSignals
|
54
|
+
def handle_user_signal(sig)
|
55
|
+
case sig
|
56
|
+
when 'USR2' then log_thread_backtraces
|
57
|
+
else raise "Assertion failed - unhandled signal: #{sig.inspect}"
|
58
|
+
end
|
59
|
+
raise ContinueProcessingSignals
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_shutdown_signal(sig)
|
63
|
+
logger.info "caught SIG#{sig}, stopping hutch..."
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def log_thread_backtraces
|
69
|
+
logger.info 'Requested a VM-wide thread stack trace dump...'
|
70
|
+
Thread.list.each do |thread|
|
71
|
+
logger.info "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
72
|
+
logger.info backtrace_for(thread)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def backtrace_for(thread)
|
77
|
+
if thread.backtrace
|
78
|
+
thread.backtrace.join("\n")
|
79
|
+
else
|
80
|
+
'<no backtrace available>'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_accessor :sig_read, :sig_write
|
85
|
+
|
86
|
+
def wait_for_signal
|
87
|
+
IO.select([sig_read])
|
88
|
+
end
|
89
|
+
|
90
|
+
def register_signal_handlers
|
91
|
+
REGISTERED_SIGNALS.each do |sig|
|
92
|
+
# This needs to be reentrant, so we queue up signals to be handled
|
93
|
+
# in the run loop, rather than acting on signals here
|
94
|
+
trap(sig) do
|
95
|
+
sig_write.puts(sig)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def user_signal?(sig)
|
101
|
+
USER_SIGNALS.include?(sig)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|