exception_handling 3.0.pre.1 → 3.0.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
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 +60 -89
  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.pre.2'
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