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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/Gemfile +7 -6
- data/Gemfile.lock +26 -23
- data/README.md +0 -1
- data/Rakefile +4 -4
- data/config/exception_filters.yml +2 -3
- data/exception_handling.gemspec +12 -10
- data/lib/exception_handling/exception_catalog.rb +5 -4
- data/lib/exception_handling/exception_description.rb +28 -28
- data/lib/exception_handling/exception_info.rb +80 -72
- data/lib/exception_handling/honeybadger_callbacks.rb +41 -24
- data/lib/exception_handling/log_stub_error.rb +10 -8
- data/lib/exception_handling/mailer.rb +27 -48
- data/lib/exception_handling/methods.rb +15 -11
- data/lib/exception_handling/sensu.rb +7 -5
- data/lib/exception_handling/testing.rb +21 -19
- data/lib/exception_handling/version.rb +1 -1
- data/lib/exception_handling.rb +105 -200
- data/test/helpers/controller_helpers.rb +3 -1
- data/test/helpers/exception_helpers.rb +9 -0
- data/test/test_helper.rb +26 -21
- data/test/unit/exception_handling/exception_catalog_test.rb +15 -14
- data/test/unit/exception_handling/exception_description_test.rb +22 -31
- data/test/unit/exception_handling/exception_info_test.rb +76 -37
- data/test/unit/exception_handling/honeybadger_callbacks_test.rb +46 -9
- data/test/unit/exception_handling/log_error_stub_test.rb +6 -4
- data/test/unit/exception_handling/mailer_test.rb +8 -14
- data/test/unit/exception_handling/methods_test.rb +9 -6
- data/test/unit/exception_handling/sensu_test.rb +6 -4
- data/test/unit/exception_handling_test.rb +279 -364
- metadata +24 -24
- data/views/exception_handling/mailer/exception_notification.html.erb +0 -92
data/lib/exception_handling.rb
CHANGED
@@ -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 #{
|
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 #{
|
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 #{
|
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 #{
|
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, "#{
|
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, "#{
|
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
|
-
#
|
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,
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
170
|
-
send_exception_to_honeybadger(exception_info)
|
171
|
-
end
|
169
|
+
send_external_notifications(exception_info)
|
172
170
|
|
173
|
-
|
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
|
-
#
|
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
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
229
|
-
results[:honeybadger_status] =
|
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
|
-
|
233
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
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
|
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
|
-
|
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 #{
|
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
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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 =>
|
342
|
-
log_error(
|
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
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
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:
|
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
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
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
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
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
|
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
|
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,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
|
-
|
72
|
+
ExceptionHandling.logger = LoggerStub.new
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
92
|
-
|
93
|
-
end
|
93
|
+
class << self
|
94
|
+
attr_writer :env
|
94
95
|
|
95
|
-
|
96
|
-
|
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
|
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
|
-
|
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#{
|
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
|
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=
|
180
|
-
if ActiveSupport::TimeWithZone
|
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
|
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?
|
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
|
-
|
195
|
-
|
198
|
+
def now
|
199
|
+
now_override ? now_override.dup : old_now
|
200
|
+
end
|
196
201
|
end
|
197
202
|
end
|