exception_handling 2.2.1 → 2.3.0.pre.1

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 (34) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +5 -0
  4. data/Gemfile +7 -6
  5. data/Gemfile.lock +26 -23
  6. data/README.md +0 -1
  7. data/Rakefile +4 -4
  8. data/config/exception_filters.yml +2 -3
  9. data/exception_handling.gemspec +12 -10
  10. data/lib/exception_handling/exception_catalog.rb +5 -4
  11. data/lib/exception_handling/exception_description.rb +28 -28
  12. data/lib/exception_handling/exception_info.rb +80 -72
  13. data/lib/exception_handling/honeybadger_callbacks.rb +41 -24
  14. data/lib/exception_handling/log_stub_error.rb +10 -8
  15. data/lib/exception_handling/mailer.rb +27 -48
  16. data/lib/exception_handling/methods.rb +15 -11
  17. data/lib/exception_handling/sensu.rb +7 -5
  18. data/lib/exception_handling/testing.rb +21 -19
  19. data/lib/exception_handling/version.rb +1 -1
  20. data/lib/exception_handling.rb +105 -200
  21. data/test/helpers/controller_helpers.rb +3 -1
  22. data/test/helpers/exception_helpers.rb +9 -0
  23. data/test/test_helper.rb +26 -21
  24. data/test/unit/exception_handling/exception_catalog_test.rb +15 -14
  25. data/test/unit/exception_handling/exception_description_test.rb +22 -31
  26. data/test/unit/exception_handling/exception_info_test.rb +76 -37
  27. data/test/unit/exception_handling/honeybadger_callbacks_test.rb +46 -9
  28. data/test/unit/exception_handling/log_error_stub_test.rb +6 -4
  29. data/test/unit/exception_handling/mailer_test.rb +8 -14
  30. data/test/unit/exception_handling/methods_test.rb +9 -6
  31. data/test/unit/exception_handling/sensu_test.rb +6 -4
  32. data/test/unit/exception_handling_test.rb +279 -364
  33. metadata +24 -24
  34. data/views/exception_handling/mailer/exception_notification.html.erb +0 -92
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'timeout'
2
4
  require 'active_support'
3
5
  require 'active_support/core_ext/hash'
@@ -17,13 +19,12 @@ require "exception_handling/honeybadger_callbacks.rb"
17
19
  _ = ActiveSupport::HashWithIndifferentAccess
18
20
 
19
21
  module ExceptionHandling # never included
20
-
21
22
  class Warning < StandardError; end
22
23
  class MailerTimeout < Timeout::Error; end
23
24
  class ClientLoggingError < StandardError; end
24
25
 
25
26
  SUMMARY_THRESHOLD = 5
26
- SUMMARY_PERIOD = 60*60 # 1.hour
27
+ SUMMARY_PERIOD = 60 * 60 # 1.hour
27
28
 
28
29
  AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'].freeze
29
30
  HONEYBADGER_STATUSES = [:success, :failure, :skipped].freeze
@@ -38,19 +39,19 @@ module ExceptionHandling # never included
38
39
  attr_writer :exception_recipients
39
40
 
40
41
  def server_name
41
- @server_name or raise ArgumentError, "You must assign a value to #{self.name}.server_name"
42
+ @server_name or raise ArgumentError, "You must assign a value to #{name}.server_name"
42
43
  end
43
44
 
44
45
  def sender_address
45
- @sender_address or raise ArgumentError, "You must assign a value to #{self.name}.sender_address"
46
+ @sender_address or raise ArgumentError, "You must assign a value to #{name}.sender_address"
46
47
  end
47
48
 
48
49
  def exception_recipients
49
- @exception_recipients or raise ArgumentError, "You must assign a value to #{self.name}.exception_recipients"
50
+ @exception_recipients or raise ArgumentError, "You must assign a value to #{name}.exception_recipients"
50
51
  end
51
52
 
52
53
  def logger
53
- @logger or raise ArgumentError, "You must assign a value to #{self.name}.logger"
54
+ @logger or raise ArgumentError, "You must assign a value to #{name}.logger"
54
55
  end
55
56
 
56
57
  def logger=(logger)
@@ -87,7 +88,6 @@ module ExceptionHandling # never included
87
88
  attr_accessor :production_support_recipients
88
89
  attr_accessor :escalation_recipients
89
90
  attr_accessor :email_environment
90
- attr_accessor :mailer_send_enabled
91
91
  attr_accessor :custom_data_hook
92
92
  attr_accessor :post_log_error_hook
93
93
  attr_accessor :stub_handler
@@ -100,7 +100,6 @@ module ExceptionHandling # never included
100
100
  attr_reader :eventmachine_synchrony
101
101
 
102
102
  @filter_list_filename = "./config/exception_filters.yml"
103
- @mailer_send_enabled = true
104
103
  @email_environment = ""
105
104
  @eventmachine_safe = false
106
105
  @eventmachine_synchrony = false
@@ -111,8 +110,9 @@ module ExceptionHandling # never included
111
110
  # set this for operation within an eventmachine reactor
112
111
  def eventmachine_safe=(bool)
113
112
  if bool != true && bool != false
114
- raise ArgumentError, "#{self.name}.eventmachine_safe must be a boolean."
113
+ raise ArgumentError, "#{name}.eventmachine_safe must be a boolean."
115
114
  end
115
+
116
116
  if bool
117
117
  require 'eventmachine'
118
118
  require 'em/protocols/smtpclient'
@@ -123,8 +123,9 @@ module ExceptionHandling # never included
123
123
  # set this for EM::Synchrony async operation
124
124
  def eventmachine_synchrony=(bool)
125
125
  if bool != true && bool != false
126
- raise ArgumentError, "#{self.name}.eventmachine_synchrony must be a boolean."
126
+ raise ArgumentError, "#{name}.eventmachine_synchrony must be a boolean."
127
127
  end
128
+
128
129
  @eventmachine_synchrony = bool
129
130
  end
130
131
 
@@ -148,67 +149,60 @@ module ExceptionHandling # never included
148
149
  # Gets called by Rack Middleware: DebugExceptions or ShowExceptions
149
150
  # it does 2 things:
150
151
  # log the error
151
- # email the error
152
+ # may send to honeybadger
152
153
  #
153
154
  # but not during functional tests, when rack middleware is not used
154
155
  #
155
- def log_error_rack(exception, env, rack_filter)
156
+ def log_error_rack(exception, env, _rack_filter)
156
157
  timestamp = set_log_error_timestamp
157
158
  exception_info = ExceptionInfo.new(exception, env, timestamp)
158
159
 
159
160
  if stub_handler
160
- return stub_handler.handle_stub_log_error(exception_info.data)
161
- end
162
-
163
- # TODO: add a more interesting custom description, like:
164
- # custom_description = ": caught and processed by Rack middleware filter #{rack_filter}"
165
- # which would be nice, but would also require changing quite a few tests
166
- custom_description = ""
167
- write_exception_to_log(exception, custom_description, timestamp)
161
+ stub_handler.handle_stub_log_error(exception_info.data)
162
+ else
163
+ # TODO: add a more interesting custom description, like:
164
+ # custom_description = ": caught and processed by Rack middleware filter #{rack_filter}"
165
+ # which would be nice, but would also require changing quite a few tests
166
+ custom_description = ""
167
+ write_exception_to_log(exception, custom_description, timestamp)
168
168
 
169
- if honeybadger?
170
- send_exception_to_honeybadger(exception_info)
171
- end
169
+ send_external_notifications(exception_info)
172
170
 
173
- if should_send_email?
174
- # controller may not exist in some cases (like most 404 errors)
175
- if (controller = exception_info.controller)
176
- controller.session["last_exception_timestamp"] = last_exception_timestamp
177
- end
178
- log_error_email(exception_info)
171
+ nil
179
172
  end
180
173
  end
181
174
 
182
175
  #
183
176
  # Normal Operation:
184
177
  # Called directly by our code, usually from rescue blocks.
185
- # Does three things: write to log file and send an email, may also send to honeybadger if defined
178
+ # Writes to log file and may send to honeybadger
186
179
  #
187
180
  # Functional Test Operation:
188
- # Calls into handle_stub_log_error and returns. no log file. no email.
181
+ # Calls into handle_stub_log_error and returns. no log file. no honeybadger
189
182
  #
190
183
  def log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback)
191
- begin
192
- ex = make_exception(exception_or_string)
193
- timestamp = set_log_error_timestamp
194
- exception_info = ExceptionInfo.new(ex, exception_context, timestamp, controller || current_controller, data_callback)
195
-
196
- if stub_handler
197
- stub_handler.handle_stub_log_error(exception_info.data)
198
- else
199
- write_exception_to_log(ex, exception_context, timestamp, **log_context)
200
- external_notification_results = unless treat_like_warning || ex.is_a?(Warning)
201
- send_external_notifications(exception_info)
202
- end || {}
203
- execute_custom_log_error_callback(exception_info.enhanced_data, exception_info.exception, treat_like_warning, external_notification_results)
204
- nil
205
- end
206
- rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged
207
- raise
208
- rescue Exception => ex
209
- $stderr.puts("ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
210
- write_exception_to_log(ex, "ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
184
+ ex = make_exception(exception_or_string)
185
+ timestamp = set_log_error_timestamp
186
+ exception_info = ExceptionInfo.new(ex, exception_context, timestamp, controller || current_controller, data_callback)
187
+
188
+ if stub_handler
189
+ stub_handler.handle_stub_log_error(exception_info.data)
190
+ else
191
+ write_exception_to_log(ex, exception_context, timestamp, **log_context)
192
+ external_notification_results = unless treat_like_warning || ex.is_a?(Warning)
193
+ send_external_notifications(exception_info)
194
+ end || {}
195
+ execute_custom_log_error_callback(exception_info.enhanced_data.merge(log_context: log_context), exception_info.exception, treat_like_warning, external_notification_results)
211
196
  end
197
+
198
+ ExceptionHandling.last_exception_timestamp
199
+ rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged
200
+ raise
201
+ rescue Exception => ex
202
+ warn("ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
203
+ write_exception_to_log(ex, "ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
204
+ ensure
205
+ ExceptionHandling.last_exception_timestamp
212
206
  end
213
207
 
214
208
  #
@@ -225,34 +219,38 @@ module ExceptionHandling # never included
225
219
  #
226
220
  def send_external_notifications(exception_info)
227
221
  results = {}
228
- if honeybadger?
229
- results[:honeybadger_status] = send_exception_to_honeybadger(exception_info)
222
+ if honeybadger_defined?
223
+ results[:honeybadger_status] = send_exception_to_honeybadger_unless_filtered(exception_info)
230
224
  end
225
+ results
226
+ end
231
227
 
232
- if should_send_email?
233
- log_error_email(exception_info)
228
+ # Returns :success or :failure or :skipped
229
+ def send_exception_to_honeybadger_unless_filtered(exception_info)
230
+ if exception_info.send_to_honeybadger?
231
+ send_exception_to_honeybadger(exception_info)
232
+ else
233
+ log_info("Filtered exception using '#{exception_info.exception_description.filter_name}'; not sending notification to Honeybadger")
234
+ :skipped
234
235
  end
235
- results
236
236
  end
237
237
 
238
238
  #
239
239
  # Log exception to honeybadger.io.
240
240
  #
241
+ # Returns :success or :failure
242
+ #
241
243
  def send_exception_to_honeybadger(exception_info)
242
244
  exception = exception_info.exception
243
245
  exception_description = exception_info.exception_description
244
- if exception_info.send_to_honeybadger?
245
- response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
246
- error_message: exception.message.to_s,
247
- exception: exception,
248
- context: exception_info.honeybadger_context_data)
249
- response ? :success : :failure
250
- else
251
- log_info("Filtered exception using '#{exception_description.filter_name}'; not sending notification to Honeybadger")
252
- :skipped
253
- end
246
+ response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
247
+ error_message: exception.message.to_s,
248
+ exception: exception,
249
+ context: exception_info.honeybadger_context_data,
250
+ controller: exception_info.controller_name)
251
+ response ? :success : :failure
254
252
  rescue Exception => ex
255
- $stderr.puts("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")}")
253
+ 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")}")
256
254
  write_exception_to_log(ex, "ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.message}", exception_info.timestamp)
257
255
  :failure
258
256
  end
@@ -260,7 +258,7 @@ module ExceptionHandling # never included
260
258
  #
261
259
  # Check if Honeybadger defined.
262
260
  #
263
- def honeybadger?
261
+ def honeybadger_defined?
264
262
  Object.const_defined?("Honeybadger")
265
263
  end
266
264
 
@@ -295,7 +293,7 @@ module ExceptionHandling # never included
295
293
  yield
296
294
  rescue => ex
297
295
  log_error ex, exception_context, **log_context
298
- return nil
296
+ nil
299
297
  end
300
298
 
301
299
  def ensure_completely_safe(exception_context = "", **log_context)
@@ -304,33 +302,32 @@ module ExceptionHandling # never included
304
302
  raise
305
303
  rescue Exception => ex
306
304
  log_error ex, exception_context, log_context
305
+ nil
307
306
  end
308
307
 
309
308
  def escalate_to_production_support(exception_or_string, email_subject)
310
- production_support_recipients or raise ArgumentError, "In order to escalate to production support, you must set #{self.name}.production_recipients"
309
+ production_support_recipients or raise ArgumentError, "In order to escalate to production support, you must set #{name}.production_recipients"
311
310
  ex = make_exception(exception_or_string)
312
- escalate_custom(email_subject, ex, last_exception_timestamp, production_support_recipients)
311
+ escalate(email_subject, ex, last_exception_timestamp, production_support_recipients)
313
312
  end
314
313
 
315
- def escalate_error(exception_or_string, email_subject, **log_context)
314
+ def escalate_error(exception_or_string, email_subject, custom_recipients = nil, **log_context)
316
315
  ex = make_exception(exception_or_string)
317
316
  log_error(ex, **log_context)
318
- escalate(email_subject, ex, last_exception_timestamp)
317
+ escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
319
318
  end
320
319
 
321
- def escalate_warning(message, email_subject, **log_context)
320
+ def escalate_warning(message, email_subject, custom_recipients = nil, **log_context)
322
321
  ex = Warning.new(message)
323
322
  log_error(ex, **log_context)
324
- escalate(email_subject, ex, last_exception_timestamp)
323
+ escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
325
324
  end
326
325
 
327
- def ensure_escalation(email_subject, **log_context)
328
- begin
329
- yield
330
- rescue => ex
331
- escalate_error(ex, email_subject, **log_context)
332
- nil
333
- end
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
334
331
  end
335
332
 
336
333
  def alert_warning(exception_or_string, alert_name, exception_context, **log_context)
@@ -338,28 +335,22 @@ module ExceptionHandling # never included
338
335
  log_error(ex, exception_context, **log_context)
339
336
  begin
340
337
  ExceptionHandling::Sensu.generate_event(alert_name, exception_context.to_s + "\n" + encode_utf8(ex.message.to_s))
341
- rescue => send_ex
342
- log_error(send_ex, 'ExceptionHandling.alert_warning')
338
+ rescue => ex
339
+ log_error(ex, 'ExceptionHandling.alert_warning')
343
340
  end
344
341
  end
345
342
 
346
343
  def ensure_alert(alert_name, exception_context, **log_context)
347
- begin
348
- yield
349
- rescue => ex
350
- alert_warning(ex, alert_name, exception_context, **log_context)
351
- nil
352
- end
344
+ yield
345
+ rescue => ex
346
+ alert_warning(ex, alert_name, exception_context, **log_context)
347
+ nil
353
348
  end
354
349
 
355
350
  def set_log_error_timestamp
356
351
  ExceptionHandling.last_exception_timestamp = Time.now.to_i
357
352
  end
358
353
 
359
- def should_send_email?
360
- ExceptionHandling.mailer_send_enabled
361
- end
362
-
363
354
  def trace_timing(description)
364
355
  result = nil
365
356
  time = Benchmark.measure do
@@ -381,19 +372,19 @@ module ExceptionHandling # never included
381
372
  def encode_utf8(string)
382
373
  string.encode('UTF-8',
383
374
  replace: '?',
384
- undef: :replace,
375
+ undef: :replace,
385
376
  invalid: :replace)
386
377
  end
387
378
 
388
379
  def clean_backtrace(exception)
389
380
  backtrace = if exception.backtrace.nil?
390
- ['<no backtrace>']
391
- elsif exception.is_a?(ClientLoggingError)
392
- exception.backtrace
393
- elsif defined?(Rails) && defined?(Rails.backtrace_cleaner)
394
- Rails.backtrace_cleaner.clean(exception.backtrace)
395
- else
396
- exception.backtrace
381
+ ['<no backtrace>']
382
+ elsif exception.is_a?(ClientLoggingError)
383
+ exception.backtrace
384
+ elsif defined?(Rails) && defined?(Rails.backtrace_cleaner)
385
+ Rails.backtrace_cleaner.clean(exception.backtrace)
386
+ else
387
+ exception.backtrace
397
388
  end
398
389
 
399
390
  # The rails backtrace cleaner returns an empty array for a backtrace if the exception was raised outside the app (inside a gem for instance)
@@ -406,25 +397,6 @@ module ExceptionHandling # never included
406
397
 
407
398
  private
408
399
 
409
- def log_error_email(exception_info)
410
- data = exception_info.enhanced_data
411
- exception_description = exception_info.exception_description
412
-
413
- if exception_description && !exception_description.send_email
414
- ExceptionHandling.logger.warn(
415
- "Filtered exception using '#{exception_description.filter_name}'; not sending email to notify"
416
- )
417
- else
418
- if summarize_exception(data) != :Summarized
419
- deliver(ExceptionHandling::Mailer.exception_notification(data))
420
- end
421
- end
422
- nil
423
- rescue Exception => ex
424
- $stderr.puts("ExceptionHandling.log_error_email rescued exception while logging #{exception_info.exception_context}: #{exception_info.exception}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
425
- write_exception_to_log(ex, "ExceptionHandling.log_error_email rescued exception while logging #{exception_info.exception_context}: #{exception_info.exception}", exception_info.timestamp)
426
- end
427
-
428
400
  def execute_custom_log_error_callback(exception_data, exception, treat_like_warning, external_notification_results)
429
401
  if ExceptionHandling.post_log_error_hook
430
402
  honeybadger_status = external_notification_results[:honeybadger_status] || :skipped
@@ -437,14 +409,9 @@ module ExceptionHandling # never included
437
409
  log_info("Unable to execute custom log_error callback. #{ex_message} #{ex_backtrace}")
438
410
  end
439
411
 
440
- def escalate(email_subject, ex, timestamp)
412
+ def escalate(email_subject, ex, timestamp, custom_recipients = nil)
441
413
  exception_info = ExceptionInfo.new(ex, nil, timestamp)
442
- deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, exception_info.data))
443
- end
444
-
445
- def escalate_custom(email_subject, ex, timestamp, recipients)
446
- exception_info = ExceptionInfo.new(ex, nil, timestamp)
447
- deliver(ExceptionHandling::Mailer.escalate_custom(email_subject, exception_info.data, recipients))
414
+ deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, exception_info.data, custom_recipients))
448
415
  end
449
416
 
450
417
  def deliver(mail_object)
@@ -455,20 +422,18 @@ module ExceptionHandling # never included
455
422
  dns_deferrable = EventMachine::DNS::Resolver.resolve(smtp_settings[:address])
456
423
  dns_deferrable.callback do |addrs|
457
424
  send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
458
- async_send_method,
459
- {
460
- :host => addrs.first,
461
- :port => smtp_settings[:port],
462
- :domain => smtp_settings[:domain],
463
- :auth => {:type=>:plain, :username=>smtp_settings[:user_name], :password=>smtp_settings[:password]},
464
- :from => mail_object['from'].to_s,
465
- :to => mail_object['to'].to_s,
466
- :content => "#{mail_object}\r\n.\r\n"
467
- }
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"
468
433
  )
469
434
  send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
470
435
  end
471
- dns_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to resolv DNS for #{smtp_settings[:address]}: #{err.inspect}") }
436
+ dns_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to resolv DNS for #{smtp_settings[:address]}: #{err.inspect}") }
472
437
  end
473
438
  else
474
439
  safe_email_deliver do
@@ -478,73 +443,13 @@ module ExceptionHandling # never included
478
443
  end
479
444
 
480
445
  def safe_email_deliver
481
- Timeout::timeout 30, MailerTimeout do
446
+ Timeout.timeout 30, MailerTimeout do
482
447
  yield
483
448
  end
484
449
  rescue StandardError, MailerTimeout => ex
485
- #$stderr.puts("ExceptionHandling::safe_email_deliver rescued: #{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
486
450
  log_error(ex, "ExceptionHandling::safe_email_deliver", nil, treat_like_warning: true)
487
451
  end
488
452
 
489
- def clear_exception_summary
490
- @last_exception = nil
491
- end
492
-
493
- # Returns :Summarized iff exception has been added to summary and therefore should not be sent.
494
- def summarize_exception(data)
495
- if defined?(@last_exception) && @last_exception
496
- same_signature = @last_exception[:backtrace] == data[:backtrace]
497
-
498
- case @last_exception[:state]
499
-
500
- when :NotSummarized
501
- if same_signature
502
- @last_exception[:count] += 1
503
- if @last_exception[:count] >= SUMMARY_THRESHOLD
504
- @last_exception.merge! :state => :Summarized, :first_seen => Time.now, :count => 0
505
- end
506
- return nil
507
- end
508
-
509
- when :Summarized
510
- if same_signature
511
- @last_exception[:count] += 1
512
- if Time.now - @last_exception[:first_seen] > SUMMARY_PERIOD
513
- send_exception_summary(data, @last_exception[:first_seen], @last_exception[:count])
514
- @last_exception.merge! :first_seen => Time.now, :count => 0
515
- end
516
- return :Summarized
517
- elsif @last_exception[:count] > 0 # send the left-over, if any
518
- send_exception_summary(@last_exception[:data], @last_exception[:first_seen], @last_exception[:count])
519
- end
520
-
521
- else
522
- raise "Unknown state #{@last_exception[:state]}"
523
- end
524
- end
525
-
526
- # New signature we haven't seen before. Not summarized yet--we're just starting the count.
527
- @last_exception = {
528
- :data => data,
529
- :count => 1,
530
- :first_seen => Time.now,
531
- :backtrace => data[:backtrace],
532
- :state => :NotSummarized
533
- }
534
- nil
535
- end
536
-
537
- def send_exception_summary(exception_data, first_seen, occurrences)
538
- Timeout::timeout 30, MailerTimeout do
539
- deliver(ExceptionHandling::Mailer.exception_notification(exception_data, first_seen, occurrences))
540
- end
541
- rescue StandardError, MailerTimeout => ex
542
- original_error = exception_data[:error_string]
543
- log_prefix = "ExceptionHandling.log_error_email rescued exception while logging #{original_error}"
544
- $stderr.puts("#{log_prefix}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
545
- log_info(log_prefix)
546
- end
547
-
548
453
  def make_exception(exception_or_string)
549
454
  if exception_or_string.is_a?(Exception)
550
455
  exception_or_string
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ControllerHelpers
2
4
  DummyController = Struct.new(:complete_request_uri, :request, :session)
3
5
  DummyRequest = Struct.new(:env, :parameters, :session_options)
4
-
6
+
5
7
  class DummySession
6
8
  def initialize(data)
7
9
  @data = data
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ExceptionHelpers
2
4
  def raise_exception_with_nil_message
3
5
  raise exception_with_nil_message
@@ -8,4 +10,11 @@ module ExceptionHelpers
8
10
  stub(exception_with_nil_message).message { nil }
9
11
  exception_with_nil_message
10
12
  end
13
+
14
+ attr_reader :sent_notifications
15
+
16
+ def capture_notifications
17
+ @sent_notifications = []
18
+ stub(ExceptionHandling).send_exception_to_honeybadger(anything) { |exception_info| @sent_notifications << exception_info }
19
+ end
11
20
  end
data/test/test_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
  require 'active_support/time'
3
5
  require 'active_support/test_case'
@@ -67,11 +69,11 @@ class SocketStub
67
69
  end
68
70
  end
69
71
 
70
- ExceptionHandling.logger = LoggerStub.new
72
+ ExceptionHandling.logger = LoggerStub.new
71
73
 
72
- def dont_stub_log_error
73
- true
74
- end
74
+ def dont_stub_log_error
75
+ true
76
+ end
75
77
 
76
78
  ActionMailer::Base.delivery_method = :test
77
79
 
@@ -88,12 +90,12 @@ class ActiveSupport::TestCase
88
90
 
89
91
  unless defined?(Rails) && defined?(Rails.env)
90
92
  module ::Rails
91
- def self.env
92
- @env ||= 'test'
93
- end
93
+ class << self
94
+ attr_writer :env
94
95
 
95
- def self.env=(mode)
96
- @env = mode
96
+ def env
97
+ @env ||= 'test'
98
+ end
97
99
  end
98
100
  end
99
101
  end
@@ -107,7 +109,6 @@ class ActiveSupport::TestCase
107
109
  ExceptionHandling.exception_recipients = 'exceptions@example.com'
108
110
  ExceptionHandling.escalation_recipients = 'escalation@example.com'
109
111
  ExceptionHandling.server_name = 'server'
110
- ExceptionHandling.mailer_send_enabled = true
111
112
  ExceptionHandling.filter_list_filename = "./config/exception_filters.yml"
112
113
  ExceptionHandling.eventmachine_safe = false
113
114
  ExceptionHandling.eventmachine_synchrony = false
@@ -117,7 +118,7 @@ class ActiveSupport::TestCase
117
118
  end
118
119
 
119
120
  teardown do
120
- @@constant_overrides && @@constant_overrides.reverse.each do |parent_module, k, v|
121
+ @@constant_overrides&.reverse&.each do |parent_module, k, v|
121
122
  ExceptionHandling.ensure_safe "constant cleanup #{k.inspect}, #{parent_module}(#{parent_module.class})::#{v.inspect}(#{v.class})" do
122
123
  silence_warnings do
123
124
  if v == :never_defined
@@ -141,7 +142,11 @@ class ActiveSupport::TestCase
141
142
  parent_module == :never_defined and raise "You need to set each parent constant earlier! #{nested_const_name}"
142
143
  final_parent_module = parent_module
143
144
  final_const_name = nested_const_name
144
- parent_module.const_get(nested_const_name) rescue :never_defined
145
+ begin
146
+ parent_module.const_get(nested_const_name)
147
+ rescue
148
+ :never_defined
149
+ end
145
150
  end
146
151
 
147
152
  @@constant_overrides << [final_parent_module, final_const_name, original_value]
@@ -156,11 +161,11 @@ class ActiveSupport::TestCase
156
161
  else
157
162
  original_count = 0
158
163
  end
159
- assert_equal expected, ActionMailer::Base.deliveries.size - original_count, "wrong number of emails#{ ': ' + message.to_s if message}"
164
+ assert_equal expected, ActionMailer::Base.deliveries.size - original_count, "wrong number of emails#{': ' + message.to_s if message}"
160
165
  end
161
166
  end
162
167
 
163
- def assert_equal_with_diff arg1, arg2, msg = ''
168
+ def assert_equal_with_diff(arg1, arg2, msg = '')
164
169
  if arg1 == arg2
165
170
  assert true # To keep the assertion count accurate
166
171
  else
@@ -176,22 +181,22 @@ class Time
176
181
  class << self
177
182
  attr_reader :now_override
178
183
 
179
- def now_override= override_time
180
- if ActiveSupport::TimeWithZone === override_time
184
+ def now_override=(override_time)
185
+ if override_time.is_a?(ActiveSupport::TimeWithZone)
181
186
  override_time = override_time.localtime
182
187
  else
183
- override_time.nil? || Time === override_time or raise "override_time should be a Time object, but was a #{override_time.class.name}"
188
+ override_time.nil? || override_time.is_a?(Time) or raise "override_time should be a Time object, but was a #{override_time.class.name}"
184
189
  end
185
190
  @now_override = override_time
186
191
  end
187
192
 
188
- unless defined? @@_old_now_defined
193
+ unless defined?(@@_old_now_defined)
189
194
  alias old_now now
190
195
  @@_old_now_defined = true
191
196
  end
192
- end
193
197
 
194
- def self.now
195
- now_override ? now_override.dup : old_now
198
+ def now
199
+ now_override ? now_override.dup : old_now
200
+ end
196
201
  end
197
202
  end