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.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +11 -12
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +118 -1
  7. data/Gemfile +15 -4
  8. data/Guardfile +13 -4
  9. data/README.md +274 -24
  10. data/Rakefile +8 -1
  11. data/hutch.gemspec +6 -7
  12. data/lib/hutch.rb +11 -8
  13. data/lib/hutch/adapters/march_hare.rb +1 -1
  14. data/lib/hutch/broker.rb +113 -110
  15. data/lib/hutch/cli.rb +42 -11
  16. data/lib/hutch/config.rb +209 -59
  17. data/lib/hutch/error_handlers.rb +1 -0
  18. data/lib/hutch/error_handlers/airbrake.rb +44 -16
  19. data/lib/hutch/error_handlers/base.rb +15 -0
  20. data/lib/hutch/error_handlers/honeybadger.rb +33 -18
  21. data/lib/hutch/error_handlers/logger.rb +12 -6
  22. data/lib/hutch/error_handlers/opbeat.rb +30 -0
  23. data/lib/hutch/error_handlers/sentry.rb +14 -6
  24. data/lib/hutch/logging.rb +5 -5
  25. data/lib/hutch/publisher.rb +75 -0
  26. data/lib/hutch/tracers.rb +1 -0
  27. data/lib/hutch/tracers/opbeat.rb +37 -0
  28. data/lib/hutch/version.rb +1 -1
  29. data/lib/hutch/waiter.rb +104 -0
  30. data/lib/hutch/worker.rb +50 -66
  31. data/lib/yard-settings/handler.rb +38 -0
  32. data/lib/yard-settings/yard-settings.rb +2 -0
  33. data/spec/hutch/broker_spec.rb +162 -77
  34. data/spec/hutch/cli_spec.rb +16 -3
  35. data/spec/hutch/config_spec.rb +83 -22
  36. data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
  37. data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
  38. data/spec/hutch/error_handlers/logger_spec.rb +14 -1
  39. data/spec/hutch/error_handlers/opbeat_spec.rb +37 -0
  40. data/spec/hutch/error_handlers/sentry_spec.rb +18 -1
  41. data/spec/hutch/logger_spec.rb +12 -6
  42. data/spec/hutch/waiter_spec.rb +51 -0
  43. data/spec/hutch/worker_spec.rb +33 -4
  44. data/spec/spec_helper.rb +7 -5
  45. data/spec/tracers/opbeat_spec.rb +44 -0
  46. data/templates/default/class/html/settings.erb +0 -0
  47. data/templates/default/class/setup.rb +4 -0
  48. data/templates/default/fulldoc/html/css/hutch.css +13 -0
  49. data/templates/default/layout/html/setup.rb +7 -0
  50. data/templates/default/method_details/html/settings.erb +5 -0
  51. data/templates/default/method_details/setup.rb +4 -0
  52. data/templates/default/method_details/text/settings.erb +0 -0
  53. data/templates/default/module/html/settings.erb +40 -0
  54. data/templates/default/module/setup.rb +4 -0
  55. 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(message_id, payload, consumer, ex)
10
- prefix = "message(#{message_id || '-'}): "
11
- logger.error prefix + "Logging event to Airbrake"
12
- logger.error prefix + "#{ex.class} - #{ex.message}"
13
- ::Airbrake.notify_or_ignore(ex, {
14
- :error_class => ex.class.name,
15
- :error_message => "#{ ex.class.name }: #{ ex.message }",
16
- :backtrace => ex.backtrace,
17
- :parameters => {
18
- :payload => payload,
19
- :consumer => consumer,
20
- },
21
- :cgi_data => ENV.to_hash,
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
- class Honeybadger
7
- include Logging
7
+ # Error handler for the Honeybadger.io service
8
+ class Honeybadger < Base
8
9
 
9
- def handle(message_id, payload, consumer, ex)
10
- prefix = "message(#{message_id || '-'}): "
11
- logger.error prefix + "Logging event to Honeybadger"
12
- logger.error prefix + "#{ex.class} - #{ex.message}"
13
- ::Honeybadger.notify_or_ignore(
14
- :error_class => ex.class.name,
15
- :error_message => "#{ ex.class.name }: #{ ex.message }",
16
- :backtrace => ex.backtrace,
17
- :context => {
18
- :message_id => message_id,
19
- :consumer => consumer
20
- },
21
- :parameters => {
22
- :payload => payload
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(message_id, payload, consumer, ex)
9
- prefix = "message(#{message_id || '-'}): "
10
- logger.error prefix + "error in consumer '#{consumer}'"
11
- logger.error prefix + "#{ex.class} - #{ex.message}"
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(message_id, payload, consumer, ex)
16
- prefix = "message(#{message_id || '-'}): "
17
- logger.error prefix + "Logging event to Sentry"
18
- logger.error prefix + "#{ex.class} - #{ex.message}"
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
@@ -9,12 +9,12 @@ module Hutch
9
9
  end
10
10
  end
11
11
 
12
- def self.setup_logger(target = $stdout)
12
+ def self.setup_logger
13
13
  require 'hutch/config'
14
- @logger = Logger.new(target)
15
- @logger.level = Hutch::Config.log_level
16
- @logger.formatter = HutchFormatter.new
17
- @logger
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
@@ -2,5 +2,6 @@ module Hutch
2
2
  module Tracers
3
3
  autoload :NullTracer, 'hutch/tracers/null_tracer'
4
4
  autoload :NewRelic, 'hutch/tracers/newrelic'
5
+ autoload :Opbeat, 'hutch/tracers/opbeat'
5
6
  end
6
7
  end
@@ -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
@@ -1,4 +1,4 @@
1
1
  module Hutch
2
- VERSION = '0.21.0'.freeze
2
+ VERSION = '0.25.0'.freeze
3
3
  end
4
4
 
@@ -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