exception_handling 3.0.pre.1 → 3.0.0

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 (51) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/workflows/pipeline.yml +36 -0
  4. data/.gitignore +3 -0
  5. data/.rspec +3 -0
  6. data/.ruby-version +1 -1
  7. data/.tool-versions +1 -0
  8. data/Appraisals +13 -0
  9. data/CHANGELOG.md +150 -0
  10. data/Gemfile +10 -16
  11. data/Gemfile.lock +65 -128
  12. data/README.md +51 -19
  13. data/Rakefile +8 -11
  14. data/exception_handling.gemspec +11 -13
  15. data/gemfiles/rails_5.gemfile +16 -0
  16. data/gemfiles/rails_6.gemfile +16 -0
  17. data/gemfiles/rails_7.gemfile +16 -0
  18. data/lib/exception_handling/escalate_callback.rb +19 -0
  19. data/lib/exception_handling/exception_info.rb +15 -11
  20. data/lib/exception_handling/log_stub_error.rb +2 -1
  21. data/lib/exception_handling/logging_methods.rb +21 -0
  22. data/lib/exception_handling/testing.rb +9 -12
  23. data/lib/exception_handling/version.rb +1 -1
  24. data/lib/exception_handling.rb +83 -173
  25. data/{test → spec}/helpers/exception_helpers.rb +2 -2
  26. data/spec/rake_test_warning_false.rb +20 -0
  27. data/{test/test_helper.rb → spec/spec_helper.rb} +63 -66
  28. data/spec/unit/exception_handling/escalate_callback_spec.rb +81 -0
  29. data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
  30. data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
  31. data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +170 -114
  32. data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
  33. data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
  34. data/spec/unit/exception_handling_spec.rb +1063 -0
  35. metadata +62 -91
  36. data/lib/exception_handling/honeybadger_callbacks.rb +0 -59
  37. data/lib/exception_handling/mailer.rb +0 -70
  38. data/lib/exception_handling/methods.rb +0 -101
  39. data/lib/exception_handling/sensu.rb +0 -28
  40. data/semaphore_ci/setup.sh +0 -3
  41. data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
  42. data/test/unit/exception_handling/exception_description_test.rb +0 -82
  43. data/test/unit/exception_handling/honeybadger_callbacks_test.rb +0 -122
  44. data/test/unit/exception_handling/mailer_test.rb +0 -98
  45. data/test/unit/exception_handling/methods_test.rb +0 -84
  46. data/test/unit/exception_handling/sensu_test.rb +0 -52
  47. data/test/unit/exception_handling_test.rb +0 -1109
  48. data/views/exception_handling/mailer/escalate_custom.html.erb +0 -17
  49. data/views/exception_handling/mailer/escalation_notification.html.erb +0 -17
  50. data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +0 -82
  51. /data/{test → spec}/helpers/controller_helpers.rb +0 -0
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 5.2"
6
+ gem "appraisal", "~> 2.2"
7
+ gem "honeybadger", "~> 4.11"
8
+ gem "pry"
9
+ gem "pry-byebug"
10
+ gem "rake"
11
+ gem "rspec"
12
+ gem "rspec_junit_formatter"
13
+ gem "rubocop"
14
+ gem "test-unit"
15
+
16
+ gemspec path: "../"
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 6.0"
6
+ gem "appraisal", "~> 2.2"
7
+ gem "honeybadger", "~> 4.11"
8
+ gem "pry"
9
+ gem "pry-byebug"
10
+ gem "rake"
11
+ gem "rspec"
12
+ gem "rspec_junit_formatter"
13
+ gem "rubocop"
14
+ gem "test-unit"
15
+
16
+ gemspec path: "../"
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 7.0"
6
+ gem "appraisal", "~> 2.2"
7
+ gem "honeybadger", "~> 4.11"
8
+ gem "pry"
9
+ gem "pry-byebug"
10
+ gem "rake"
11
+ gem "rspec"
12
+ gem "rspec_junit_formatter"
13
+ gem "rubocop"
14
+ gem "test-unit"
15
+
16
+ gemspec path: "../"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'escalate'
4
+
5
+ module ExceptionHandling
6
+ module EscalateCallback
7
+ class << self
8
+ def register_if_configured!
9
+ register! if ::ExceptionHandling.configured?
10
+ end
11
+
12
+ def register!
13
+ Escalate.on_escalate(log_first: false) do |exception, location_message, **context|
14
+ ::ExceptionHandling.log_error(exception, location_message, **context)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -3,7 +3,7 @@
3
3
  module ExceptionHandling
4
4
  class ExceptionInfo
5
5
 
6
- ENVIRONMENT_WHITELIST = [
6
+ ENVIRONMENT_ALLOWLIST = [
7
7
  /^HTTP_/,
8
8
  /^QUERY_/,
9
9
  /^REQUEST_/,
@@ -46,16 +46,20 @@ module ExceptionHandling
46
46
  EOS
47
47
 
48
48
  SECTIONS = [:request, :session, :environment, :backtrace, :event_response].freeze
49
- HONEYBADGER_CONTEXT_SECTIONS = [:timestamp, :error_class, :exception_context, :server, :scm_revision, :notes, :user_details, :request, :session, :environment, :backtrace, :event_response].freeze
49
+ HONEYBADGER_CONTEXT_SECTIONS = [:timestamp, :error_class, :exception_context, :server, :scm_revision, :notes,
50
+ :user_details, :request, :session, :environment, :backtrace, :event_response, :log_context].freeze
50
51
 
51
- attr_reader :exception, :controller, :exception_context, :timestamp
52
+ attr_reader :exception, :controller, :exception_context, :timestamp, :honeybadger_tags
52
53
 
53
- def initialize(exception, exception_context, timestamp, controller = nil, data_callback = nil)
54
+ def initialize(exception, exception_context, timestamp, controller: nil, data_callback: nil, log_context: nil)
54
55
  @exception = exception
55
56
  @exception_context = exception_context
56
57
  @timestamp = timestamp
57
58
  @controller = controller || controller_from_context(exception_context)
58
59
  @data_callback = data_callback
60
+ # merge into the surrounding context just like ContextualLogger does when logging
61
+ @merged_log_context = ExceptionHandling.logger.current_context_for_thread.deep_merge(log_context || {})
62
+ @honeybadger_tags = Array(@merged_log_context[:honeybadger_tags] || [])
59
63
  end
60
64
 
61
65
  def data
@@ -79,9 +83,10 @@ module ExceptionHandling
79
83
  end
80
84
 
81
85
  def controller_name
82
- @controller_name ||= if @controller
83
- @controller.request.parameters.with_indifferent_access[:controller]
84
- end.to_s
86
+ @controller_name ||= (
87
+ @merged_log_context[:honeybadger_grouping] ||
88
+ (@controller && @controller.request.parameters.with_indifferent_access[:controller])
89
+ ).to_s
85
90
  end
86
91
 
87
92
  private
@@ -176,7 +181,7 @@ module ExceptionHandling
176
181
 
177
182
  def clean_environment(env)
178
183
  Hash[ env.map do |k, v|
179
- [k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) && ENVIRONMENT_WHITELIST.any? { |regex| k =~ regex }
184
+ [k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) && ENVIRONMENT_ALLOWLIST.any? { |regex| k =~ regex }
180
185
  end.compact ]
181
186
  end
182
187
 
@@ -267,14 +272,13 @@ module ExceptionHandling
267
272
  data = enhanced_data.dup
268
273
  data[:server] = ExceptionHandling.server_name
269
274
  data[:exception_context] = deep_clean_hash(@exception_context) if @exception_context.present?
275
+ data[:log_context] = @merged_log_context
270
276
  unstringify_sections(data)
271
- context_data = HONEYBADGER_CONTEXT_SECTIONS.reduce({}) do |context, section|
277
+ HONEYBADGER_CONTEXT_SECTIONS.each_with_object({}) do |section, context|
272
278
  if data[section].present?
273
279
  context[section] = data[section]
274
280
  end
275
- context
276
281
  end
277
- context_data
278
282
  end
279
283
  end
280
284
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Used by functional tests to track exceptions.
4
+ # Test Helper that supports Minitest::Test and Test::Unit
5
+ # Used by tests in the consumers of this gem to track exceptions.
5
6
  #
6
7
 
7
8
  module LogErrorStub
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/module/delegation.rb'
5
+
6
+ module ExceptionHandling
7
+ module LoggingMethods # included on models and controllers
8
+ extend ActiveSupport::Concern
9
+
10
+ protected
11
+
12
+ delegate :log_error_rack, :log_warning, :log_info, :log_debug, :log_error, to: ExceptionHandling
13
+
14
+ def ensure_safe(exception_context = "")
15
+ yield
16
+ rescue => ex
17
+ log_error ex, exception_context
18
+ nil
19
+ end
20
+ end
21
+ end
@@ -4,7 +4,7 @@
4
4
 
5
5
  module ExceptionHandling
6
6
  module Testing
7
- class ControllerStub
7
+ class ControllerStubBase
8
8
 
9
9
  class Request
10
10
  attr_accessor :parameters, :protocol, :host, :request_uri, :env, :session_options
@@ -25,7 +25,7 @@ module ExceptionHandling
25
25
  attr_accessor :around_filter_method
26
26
 
27
27
  def around_filter(method)
28
- ControllerStub.around_filter_method = method
28
+ self.around_filter_method = method
29
29
  end
30
30
  end
31
31
 
@@ -44,14 +44,6 @@ module ExceptionHandling
44
44
  end
45
45
  end
46
46
 
47
- def simulate_around_filter(&block)
48
- set_current_controller(&block)
49
- end
50
-
51
- def controller_name
52
- "ControllerStub"
53
- end
54
-
55
47
  def action_name
56
48
  "test_action"
57
49
  end
@@ -59,9 +51,14 @@ module ExceptionHandling
59
51
  def complete_request_uri
60
52
  "#{@request.protocol}#{@request.host}#{@request.request_uri}"
61
53
  end
54
+ end
55
+
56
+ class LoggingMethodsControllerStub < ControllerStubBase
57
+ include ExceptionHandling::LoggingMethods
62
58
 
63
- include ExceptionHandling::Methods
64
- set_long_controller_action_timeout 2
59
+ def controller_name
60
+ "LoggingMethodsControllerStub"
61
+ end
65
62
  end
66
63
  end
67
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExceptionHandling
4
- VERSION = '3.0.pre.1'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -1,26 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest'
3
4
  require 'timeout'
4
5
  require 'active_support'
5
- require 'active_support/core_ext/hash'
6
+ require 'active_support/core_ext'
6
7
  require 'contextual_logger'
8
+ require 'yaml'
7
9
 
8
10
  require 'invoca/utils'
9
11
 
10
- require "exception_handling/mailer"
11
- require "exception_handling/sensu"
12
- require "exception_handling/methods"
13
- require "exception_handling/log_stub_error"
14
- require "exception_handling/exception_description"
15
- require "exception_handling/exception_catalog"
16
- require "exception_handling/exception_info"
17
- require "exception_handling/honeybadger_callbacks.rb"
12
+ require 'exception_handling/logging_methods'
13
+ require 'exception_handling/log_stub_error'
14
+ require 'exception_handling/exception_description'
15
+ require 'exception_handling/exception_catalog'
16
+ require 'exception_handling/exception_info'
17
+ require 'exception_handling/escalate_callback'
18
18
 
19
19
  _ = ActiveSupport::HashWithIndifferentAccess
20
20
 
21
21
  module ExceptionHandling # never included
22
22
  class Warning < StandardError; end
23
- class MailerTimeout < Timeout::Error; end
24
23
  class ClientLoggingError < StandardError; end
25
24
 
26
25
  SUMMARY_THRESHOLD = 5
@@ -35,19 +34,13 @@ module ExceptionHandling # never included
35
34
  # required settings
36
35
  #
37
36
  attr_writer :server_name
38
- attr_writer :sender_address
39
- attr_writer :exception_recipients
40
37
 
41
38
  def server_name
42
39
  @server_name or raise ArgumentError, "You must assign a value to #{name}.server_name"
43
40
  end
44
41
 
45
- def sender_address
46
- @sender_address or raise ArgumentError, "You must assign a value to #{name}.sender_address"
47
- end
48
-
49
- def exception_recipients
50
- @exception_recipients or raise ArgumentError, "You must assign a value to #{name}.exception_recipients"
42
+ def configured?
43
+ !@logger.nil?
51
44
  end
52
45
 
53
46
  def logger
@@ -55,79 +48,38 @@ module ExceptionHandling # never included
55
48
  end
56
49
 
57
50
  def logger=(logger)
58
- @logger = logger.is_a?(ContextualLogger) ? logger : ContextualLogger.new(logger)
51
+ logger.nil? || logger.is_a?(ContextualLogger::LoggerMixin) or raise ArgumentError,
52
+ "The logger must be a ContextualLogger::LoggerMixin, not a #{logger.class}"
53
+ @logger = logger
54
+ EscalateCallback.register_if_configured!
59
55
  end
60
56
 
61
57
  def default_metric_name(exception_data, exception, treat_like_warning)
62
- metric_name = if exception_data['metric_name']
63
- exception_data['metric_name']
64
- elsif exception.is_a?(ExceptionHandling::Warning)
65
- "warning"
66
- elsif treat_like_warning
67
- exception_name = "_#{exception.class.name.split('::').last}" if exception.present?
68
- "unforwarded_exception#{exception_name}"
69
- else
70
- "exception"
71
- end
72
-
73
- "exception_handling.#{metric_name}"
74
- end
75
-
76
- def default_honeybadger_metric_name(honeybadger_status)
77
- metric_name = if honeybadger_status.in?(HONEYBADGER_STATUSES)
78
- honeybadger_status
79
- else
80
- :unknown_status
81
- end
82
- "exception_handling.honeybadger.#{metric_name}"
58
+ if exception_data['metric_name']
59
+ exception_data['metric_name']
60
+ elsif exception.is_a?(ExceptionHandling::Warning)
61
+ "warning"
62
+ elsif treat_like_warning
63
+ exception_name = "_#{exception.class.name.split('::').last}" if exception.present?
64
+ "unforwarded_exception#{exception_name}"
65
+ else
66
+ "exception"
67
+ end
83
68
  end
84
69
 
85
70
  #
86
71
  # optional settings
87
72
  #
88
73
  attr_accessor :production_support_recipients
89
- attr_accessor :escalation_recipients
90
- attr_accessor :email_environment
74
+ attr_accessor :environment
91
75
  attr_accessor :custom_data_hook
92
76
  attr_accessor :post_log_error_hook
93
77
  attr_accessor :stub_handler
94
- attr_accessor :sensu_host
95
- attr_accessor :sensu_port
96
- attr_accessor :sensu_prefix
97
78
 
98
79
  attr_reader :filter_list_filename
99
- attr_reader :eventmachine_safe
100
- attr_reader :eventmachine_synchrony
80
+ attr_reader :honeybadger_auto_tagger
101
81
 
102
82
  @filter_list_filename = "./config/exception_filters.yml"
103
- @email_environment = ""
104
- @eventmachine_safe = false
105
- @eventmachine_synchrony = false
106
- @sensu_host = "127.0.0.1"
107
- @sensu_port = 3030
108
- @sensu_prefix = ""
109
-
110
- # set this for operation within an eventmachine reactor
111
- def eventmachine_safe=(bool)
112
- if bool != true && bool != false
113
- raise ArgumentError, "#{name}.eventmachine_safe must be a boolean."
114
- end
115
-
116
- if bool
117
- require 'eventmachine'
118
- require 'em/protocols/smtpclient'
119
- end
120
- @eventmachine_safe = bool
121
- end
122
-
123
- # set this for EM::Synchrony async operation
124
- def eventmachine_synchrony=(bool)
125
- if bool != true && bool != false
126
- raise ArgumentError, "#{name}.eventmachine_synchrony must be a boolean."
127
- end
128
-
129
- @eventmachine_synchrony = bool
130
- end
131
83
 
132
84
  def filter_list_filename=(filename)
133
85
  @filter_list_filename = filename
@@ -138,6 +90,14 @@ module ExceptionHandling # never included
138
90
  @exception_catalog ||= ExceptionCatalog.new(@filter_list_filename)
139
91
  end
140
92
 
93
+ # rubocop:disable Style/TrivialAccessors
94
+ # @param value [Proc|nil] Proc that accepts 1 parameter that will be the exception object or nil to disable the auto-tagger.
95
+ # The proc is always expected to return an array of strings. The array can be empty.
96
+ def honeybadger_auto_tagger=(value)
97
+ @honeybadger_auto_tagger = value
98
+ end
99
+ # rubocop:enable Style/TrivialAccessors
100
+
141
101
  #
142
102
  # internal settings (don't set directly)
143
103
  #
@@ -177,13 +137,18 @@ module ExceptionHandling # never included
177
137
  # Called directly by our code, usually from rescue blocks.
178
138
  # Writes to log file and may send to honeybadger
179
139
  #
140
+ # TODO: the **log_context means we can never have context named treat_like_warning. In general, keyword args will be conflated with log_context.
141
+ # Ideally we'd separate to log_context from the other keywords so they don't interfere in any way. Or have no keyword args.
142
+ #
180
143
  # Functional Test Operation:
181
144
  # Calls into handle_stub_log_error and returns. no log file. no honeybadger
182
145
  #
183
- def log_error(exception_or_string, exception_context = '', treat_like_warning: false, **log_context, &data_callback)
146
+ def log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback)
184
147
  ex = make_exception(exception_or_string)
185
148
  timestamp = set_log_error_timestamp
186
- exception_info = ExceptionInfo.new(ex, exception_context, timestamp, current_controller, data_callback)
149
+ exception_info = ExceptionInfo.new(ex, exception_context, timestamp,
150
+ controller: controller || current_controller, data_callback: data_callback,
151
+ log_context: log_context)
187
152
 
188
153
  if stub_handler
189
154
  stub_handler.handle_stub_log_error(exception_info.data)
@@ -210,7 +175,13 @@ module ExceptionHandling # never included
210
175
  #
211
176
  def write_exception_to_log(ex, exception_context, timestamp, log_context = {})
212
177
  ActiveSupport::Deprecation.silence do
213
- ExceptionHandling.logger.fatal("\nExceptionHandlingError (Error:#{timestamp}) #{ex.class} #{exception_context} (#{encode_utf8(ex.message.to_s)}):\n " + clean_backtrace(ex).join("\n ") + "\n\n", log_context)
178
+ log_message = "#{exception_context}\n#{ex.class}: (#{encode_utf8(ex.message.to_s)}):\n " + clean_backtrace(ex).join("\n ") + "\n\n"
179
+
180
+ if ex.is_a?(Warning)
181
+ ExceptionHandling.logger.warn("\nExceptionHandlingWarning (Warning:#{timestamp}) #{log_message}", **log_context)
182
+ else
183
+ ExceptionHandling.logger.fatal("\nExceptionHandlingError (Error:#{timestamp}) #{log_message}", **log_context)
184
+ end
214
185
  end
215
186
  end
216
187
 
@@ -243,11 +214,16 @@ module ExceptionHandling # never included
243
214
  def send_exception_to_honeybadger(exception_info)
244
215
  exception = exception_info.exception
245
216
  exception_description = exception_info.exception_description
217
+
218
+ # Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented.
219
+ # https://github.com/honeybadger-io/honeybadger-ruby/pull/422
220
+ tags = (honeybadger_auto_tags(exception) + exception_info.honeybadger_tags).join(' ')
246
221
  response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
247
222
  error_message: exception.message.to_s,
248
223
  exception: exception,
249
224
  context: exception_info.honeybadger_context_data,
250
- controller: exception_info.controller_name)
225
+ controller: exception_info.controller_name,
226
+ tags: tags)
251
227
  response ? :success : :failure
252
228
  rescue Exception => ex
253
229
  warn("ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.message}:\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
@@ -255,6 +231,18 @@ module ExceptionHandling # never included
255
231
  :failure
256
232
  end
257
233
 
234
+ # @param exception [Exception]
235
+ #
236
+ # @return [Array<String>]
237
+ def honeybadger_auto_tags(exception)
238
+ @honeybadger_auto_tagger&.call(exception) || []
239
+ rescue => ex
240
+ traces = ex.backtrace.join("\n")
241
+ message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
242
+ ExceptionHandling.log_info(message)
243
+ []
244
+ end
245
+
258
246
  #
259
247
  # Check if Honeybadger defined.
260
248
  #
@@ -265,38 +253,41 @@ module ExceptionHandling # never included
265
253
  #
266
254
  # Expects passed in hash to only include keys which be directly set on the Honeybadger config
267
255
  #
268
- def enable_honeybadger(config = {})
256
+ def enable_honeybadger(**config)
269
257
  Bundler.require(:honeybadger)
270
- HoneybadgerCallbacks.register_callbacks
271
258
  Honeybadger.configure do |config_klass|
272
259
  config.each do |k, v|
273
- config_klass.send(:"#{k}=", v)
260
+ if k == :before_notify
261
+ config_klass.send(k, v)
262
+ else
263
+ config_klass.send(:"#{k}=", v)
264
+ end
274
265
  end
275
266
  end
276
267
  end
277
268
 
278
- def log_warning(message, log_context = {})
269
+ def log_warning(message, **log_context)
279
270
  warning = Warning.new(message)
280
271
  warning.set_backtrace([])
281
272
  log_error(warning, **log_context)
282
273
  end
283
274
 
284
- def log_info(message, log_context = {})
285
- ExceptionHandling.logger.info(message, log_context)
275
+ def log_info(message, **log_context)
276
+ ExceptionHandling.logger.info(message, **log_context)
286
277
  end
287
278
 
288
- def log_debug(message, log_context = {})
289
- ExceptionHandling.logger.debug(message, log_context)
279
+ def log_debug(message, **log_context)
280
+ ExceptionHandling.logger.debug(message, **log_context)
290
281
  end
291
282
 
292
- def ensure_safe(exception_context = "", log_context = {})
283
+ def ensure_safe(exception_context = "", **log_context)
293
284
  yield
294
285
  rescue => ex
295
286
  log_error(ex, exception_context, **log_context)
296
287
  nil
297
288
  end
298
289
 
299
- def ensure_completely_safe(exception_context = "", log_context = {})
290
+ def ensure_completely_safe(exception_context = "", **log_context)
300
291
  yield
301
292
  rescue SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException
302
293
  raise
@@ -305,48 +296,6 @@ module ExceptionHandling # never included
305
296
  nil
306
297
  end
307
298
 
308
- def escalate_to_production_support(exception_or_string, email_subject)
309
- production_support_recipients or raise ArgumentError, "In order to escalate to production support, you must set #{name}.production_recipients"
310
- ex = make_exception(exception_or_string)
311
- escalate(email_subject, ex, last_exception_timestamp, production_support_recipients)
312
- end
313
-
314
- def escalate_error(exception_or_string, email_subject, custom_recipients = nil, log_context = {})
315
- ex = make_exception(exception_or_string)
316
- log_error(ex, **log_context)
317
- escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
318
- end
319
-
320
- def escalate_warning(message, email_subject, custom_recipients = nil, log_context = {})
321
- ex = Warning.new(message)
322
- log_error(ex, **log_context)
323
- escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
324
- end
325
-
326
- def ensure_escalation(email_subject, custom_recipients = nil, log_context = {})
327
- yield
328
- rescue => ex
329
- escalate_error(ex, email_subject, custom_recipients, log_context)
330
- nil
331
- end
332
-
333
- def alert_warning(exception_or_string, alert_name, exception_context, log_context)
334
- ex = make_exception(exception_or_string)
335
- log_error(ex, exception_context, **log_context)
336
- begin
337
- ExceptionHandling::Sensu.generate_event(alert_name, exception_context.to_s + "\n" + encode_utf8(ex.message.to_s))
338
- rescue => ex
339
- log_error(ex, 'ExceptionHandling.alert_warning')
340
- end
341
- end
342
-
343
- def ensure_alert(alert_name, exception_context, log_context = {})
344
- yield
345
- rescue => ex
346
- alert_warning(ex, alert_name, exception_context, log_context)
347
- nil
348
- end
349
-
350
299
  def set_log_error_timestamp
351
300
  ExceptionHandling.last_exception_timestamp = Time.now.to_i
352
301
  end
@@ -360,7 +309,7 @@ module ExceptionHandling # never included
360
309
  result
361
310
  end
362
311
 
363
- def log_periodically(exception_key, interval, message, log_context = {})
312
+ def log_periodically(exception_key, interval, message, **log_context)
364
313
  self.periodic_exception_intervals ||= {}
365
314
  last_logged = self.periodic_exception_intervals[exception_key]
366
315
  if !last_logged || ((last_logged + interval) < Time.now)
@@ -409,47 +358,6 @@ module ExceptionHandling # never included
409
358
  log_info("Unable to execute custom log_error callback. #{ex_message} #{ex_backtrace}")
410
359
  end
411
360
 
412
- def escalate(email_subject, ex, timestamp, custom_recipients = nil)
413
- exception_info = ExceptionInfo.new(ex, nil, timestamp)
414
- deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, exception_info.data, custom_recipients))
415
- end
416
-
417
- def deliver(mail_object)
418
- if ExceptionHandling.eventmachine_safe
419
- EventMachine.schedule do # in case we're running outside the reactor
420
- async_send_method = ExceptionHandling.eventmachine_synchrony ? :asend : :send
421
- smtp_settings = ActionMailer::Base.smtp_settings
422
- dns_deferrable = EventMachine::DNS::Resolver.resolve(smtp_settings[:address])
423
- dns_deferrable.callback do |addrs|
424
- send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
425
- async_send_method,
426
- host: addrs.first,
427
- port: smtp_settings[:port],
428
- domain: smtp_settings[:domain],
429
- auth: { type: :plain, username: smtp_settings[:user_name], password: smtp_settings[:password] },
430
- from: mail_object['from'].to_s,
431
- to: mail_object['to'].to_s,
432
- content: "#{mail_object}\r\n.\r\n"
433
- )
434
- send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
435
- end
436
- dns_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to resolv DNS for #{smtp_settings[:address]}: #{err.inspect}") }
437
- end
438
- else
439
- safe_email_deliver do
440
- mail_object.deliver_now
441
- end
442
- end
443
- end
444
-
445
- def safe_email_deliver
446
- Timeout.timeout 30, MailerTimeout do
447
- yield
448
- end
449
- rescue StandardError, MailerTimeout => ex
450
- log_error(ex, "ExceptionHandling::safe_email_deliver", treat_like_warning: true)
451
- end
452
-
453
361
  def make_exception(exception_or_string)
454
362
  if exception_or_string.is_a?(Exception)
455
363
  exception_or_string
@@ -463,4 +371,6 @@ module ExceptionHandling # never included
463
371
  end
464
372
  end
465
373
  end
374
+
375
+ EscalateCallback.register_if_configured!
466
376
  end
@@ -7,7 +7,7 @@ module ExceptionHelpers
7
7
 
8
8
  def exception_with_nil_message
9
9
  exception_with_nil_message = RuntimeError.new(nil)
10
- stub(exception_with_nil_message).message { nil }
10
+ allow(exception_with_nil_message).to receive(:message).and_return(nil)
11
11
  exception_with_nil_message
12
12
  end
13
13
 
@@ -15,6 +15,6 @@ module ExceptionHelpers
15
15
 
16
16
  def capture_notifications
17
17
  @sent_notifications = []
18
- stub(ExceptionHandling).send_exception_to_honeybadger(anything) { |exception_info| @sent_notifications << exception_info }
18
+ allow(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args) { |exception_info| @sent_notifications << exception_info }
19
19
  end
20
20
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rake 11+ has a misfeature where @warning = true by default
4
+ # See https://github.com/ruby/rake/pull/97/files
5
+ # This causes all tests to be run with `ruby -w`, causing a huge number of warnings
6
+ # from gems we don't control and overwhelming our test output.
7
+ # This patch reverts that.
8
+
9
+ _ = Rake::TestTask
10
+
11
+ class Rake::TestTask
12
+ module SetWarningFalseMixin
13
+ def initialize(*args)
14
+ super
15
+ self.warning = false
16
+ end
17
+ end
18
+
19
+ prepend SetWarningFalseMixin
20
+ end