exception_handling 2.2.1 → 2.3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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