exception_handling 3.0.pre.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/workflows/pipeline.yml +36 -0
  4. data/.gitignore +3 -0
  5. data/.rspec +3 -0
  6. data/.ruby-version +1 -1
  7. data/.tool-versions +1 -0
  8. data/Appraisals +13 -0
  9. data/CHANGELOG.md +150 -0
  10. data/Gemfile +10 -16
  11. data/Gemfile.lock +65 -128
  12. data/README.md +51 -19
  13. data/Rakefile +8 -11
  14. data/exception_handling.gemspec +11 -13
  15. data/gemfiles/rails_5.gemfile +16 -0
  16. data/gemfiles/rails_6.gemfile +16 -0
  17. data/gemfiles/rails_7.gemfile +16 -0
  18. data/lib/exception_handling/escalate_callback.rb +19 -0
  19. data/lib/exception_handling/exception_info.rb +15 -11
  20. data/lib/exception_handling/log_stub_error.rb +2 -1
  21. data/lib/exception_handling/logging_methods.rb +21 -0
  22. data/lib/exception_handling/testing.rb +9 -12
  23. data/lib/exception_handling/version.rb +1 -1
  24. data/lib/exception_handling.rb +83 -173
  25. data/{test → spec}/helpers/exception_helpers.rb +2 -2
  26. data/spec/rake_test_warning_false.rb +20 -0
  27. data/{test/test_helper.rb → spec/spec_helper.rb} +63 -66
  28. data/spec/unit/exception_handling/escalate_callback_spec.rb +81 -0
  29. data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
  30. data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
  31. data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +170 -114
  32. data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
  33. data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
  34. data/spec/unit/exception_handling_spec.rb +1063 -0
  35. metadata +62 -91
  36. data/lib/exception_handling/honeybadger_callbacks.rb +0 -59
  37. data/lib/exception_handling/mailer.rb +0 -70
  38. data/lib/exception_handling/methods.rb +0 -101
  39. data/lib/exception_handling/sensu.rb +0 -28
  40. data/semaphore_ci/setup.sh +0 -3
  41. data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
  42. data/test/unit/exception_handling/exception_description_test.rb +0 -82
  43. data/test/unit/exception_handling/honeybadger_callbacks_test.rb +0 -122
  44. data/test/unit/exception_handling/mailer_test.rb +0 -98
  45. data/test/unit/exception_handling/methods_test.rb +0 -84
  46. data/test/unit/exception_handling/sensu_test.rb +0 -52
  47. data/test/unit/exception_handling_test.rb +0 -1109
  48. data/views/exception_handling/mailer/escalate_custom.html.erb +0 -17
  49. data/views/exception_handling/mailer/escalation_notification.html.erb +0 -17
  50. data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +0 -82
  51. /data/{test → spec}/helpers/controller_helpers.rb +0 -0
@@ -1,1109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('../test_helper', __dir__)
4
- require_test_helper 'controller_helpers'
5
- require_test_helper 'exception_helpers'
6
-
7
- class ExceptionHandlingTest < ActiveSupport::TestCase
8
- include ControllerHelpers
9
- include ExceptionHelpers
10
-
11
- def dont_stub_log_error
12
- true
13
- end
14
-
15
- def append_organization_info_config(data)
16
- data[:user_details] = {}
17
- data[:user_details][:username] = "CaryP"
18
- data[:user_details][:organization] = "Invoca Engineering Dept."
19
- rescue StandardError
20
- # don't let these out!
21
- end
22
-
23
- def custom_data_callback_returns_nil_message_exception(_data)
24
- raise_exception_with_nil_message
25
- end
26
-
27
- def log_error_callback(_data, _ex, _treat_like_warning, _honeybadger_status)
28
- @fail_count += 1
29
- end
30
-
31
- def log_error_callback_config(data, _ex, treat_like_warning, honeybadger_status)
32
- @callback_data = data
33
- @treat_like_warning = treat_like_warning
34
- @fail_count += 1
35
- @honeybadger_status = honeybadger_status
36
- end
37
-
38
- def log_error_callback_with_failure(_data, _ex)
39
- raise "this should be rescued"
40
- end
41
-
42
- def log_error_callback_returns_nil_message_exception(_data, _ex)
43
- raise_exception_with_nil_message
44
- end
45
-
46
- module EventMachineStub
47
- class << self
48
- attr_accessor :block
49
-
50
- def schedule(&block)
51
- @block = block
52
- end
53
- end
54
- end
55
-
56
- class DNSResolvStub
57
- class << self
58
- attr_accessor :callback_block
59
- attr_accessor :errback_block
60
-
61
- def resolve(_hostname)
62
- self
63
- end
64
-
65
- def callback(&block)
66
- @callback_block = block
67
- end
68
-
69
- def errback(&block)
70
- @errback_block = block
71
- end
72
- end
73
- end
74
-
75
- class SmtpClientStub
76
- class << self
77
- attr_reader :block
78
- attr_reader :last_method
79
-
80
- def errback(&block)
81
- @block = block
82
- end
83
-
84
- def send_hash
85
- @send_hash ||= {}
86
- end
87
-
88
- def send(hash)
89
- @last_method = :send
90
- send_hash.clear
91
- send_hash.merge!(hash)
92
- self
93
- end
94
-
95
- def asend(hash)
96
- send(hash)
97
- @last_method = :asend
98
- self
99
- end
100
- end
101
- end
102
-
103
- class SmtpClientErrbackStub < SmtpClientStub
104
- end
105
-
106
- context "with warn and honeybadger notify stubbed" do
107
- setup do
108
- stub(ExceptionHandling).warn(anything)
109
- stub(Honeybadger).notify(anything)
110
- end
111
-
112
- context "#log_error" do
113
- should "take in additional keyword args as logging context and pass them to the logger (using preferrred log_context:)" do
114
- ExceptionHandling.log_error('This is an Error', 'This is the prefix context', service_name: 'exception_handling')
115
- assert_match(/This is an Error/, logged_excluding_reload_filter.last[:message])
116
- assert_not_empty logged_excluding_reload_filter.last[:context]
117
- assert_equal({ service_name: 'exception_handling' }, logged_excluding_reload_filter.last[:context])
118
- end
119
-
120
- should "take in additional keyword args as logging context and pass them to the logger (using **)" do
121
- ExceptionHandling.log_error('This is an Error', 'This is the prefix context', service_name: 'exception_handling')
122
- assert_match(/This is an Error/, logged_excluding_reload_filter.last[:message])
123
- assert_not_empty logged_excluding_reload_filter.last[:context]
124
- assert_equal logged_excluding_reload_filter.last[:context], service_name: 'exception_handling'
125
- end
126
- end
127
-
128
- context "#log_warning" do
129
- should "have empty array as a backtrace" do
130
- mock(ExceptionHandling).log_error(is_a(ExceptionHandling::Warning), anything) do |error|
131
- assert_equal [], error.backtrace
132
- end
133
- ExceptionHandling.log_warning('Now with empty array as a backtrace!')
134
- end
135
-
136
- should "take in additional key word args as logging context and pass them to the logger" do
137
- ExceptionHandling.log_warning('This is a Warning', service_name: 'exception_handling')
138
- assert_match(/This is a Warning/, logged_excluding_reload_filter.last[:message])
139
- assert_not_empty logged_excluding_reload_filter.last[:context]
140
- assert_equal logged_excluding_reload_filter.last[:context], service_name: 'exception_handling'
141
- end
142
- end
143
-
144
- context "#log_info" do
145
- should "take in additional key word args as logging context and pass them to the logger" do
146
- ExceptionHandling.log_warning('This is an Info', service_name: 'exception_handling')
147
- assert_match(/This is an Info/, logged_excluding_reload_filter.last[:message])
148
- assert_not_empty logged_excluding_reload_filter.last[:context]
149
- assert_equal logged_excluding_reload_filter.last[:context], service_name: 'exception_handling'
150
- end
151
- end
152
-
153
- context "#log_debug" do
154
- should "take in additional key word args as logging context and pass them to the logger" do
155
- ExceptionHandling.log_warning('This is a Debug', service_name: 'exception_handling')
156
- assert_match(/This is a Debug/, logged_excluding_reload_filter.last[:message])
157
- assert_not_empty logged_excluding_reload_filter.last[:context]
158
- assert_equal logged_excluding_reload_filter.last[:context], service_name: 'exception_handling'
159
- end
160
- end
161
-
162
- context "configuration with custom_data_hook or post_log_error_hook" do
163
- teardown do
164
- ExceptionHandling.custom_data_hook = nil
165
- ExceptionHandling.post_log_error_hook = nil
166
- end
167
-
168
- should "support a custom_data_hook" do
169
- capture_notifications
170
-
171
- ExceptionHandling.custom_data_hook = method(:append_organization_info_config)
172
- ExceptionHandling.ensure_safe("context") { raise "Some Exception" }
173
-
174
- assert_match(/Invoca Engineering Dept./, sent_notifications.last.enhanced_data['user_details'].to_s)
175
- end
176
-
177
- should "support a log_error hook, and pass exception_data, treat_like_warning, and logged_to_honeybadger to it" do
178
- @fail_count = 0
179
- @honeybadger_status = nil
180
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
181
-
182
- notify_args = []
183
- mock(Honeybadger).notify.with_any_args { |info| notify_args << info; '06220c5a-b471-41e5-baeb-de247da45a56' }
184
- ExceptionHandling.ensure_safe("context") { raise "Some Exception" }
185
- assert_equal 1, @fail_count
186
- assert_equal false, @treat_like_warning
187
- assert_equal :success, @honeybadger_status
188
-
189
- assert_equal "this is used by a test", @callback_data["notes"]
190
- assert_equal 1, notify_args.size, notify_args.inspect
191
- assert_match(/this is used by a test/, notify_args.last[:context].to_s)
192
- end
193
-
194
- should "plumb treat_like_warning and logged_to_honeybadger to log error hook" do
195
- @fail_count = 0
196
- @honeybadger_status = nil
197
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
198
- ExceptionHandling.log_error(StandardError.new("Some Exception"), "mooo", treat_like_warning: true)
199
- assert_equal 1, @fail_count
200
- assert_equal true, @treat_like_warning
201
- assert_equal :skipped, @honeybadger_status
202
- end
203
-
204
- should "include logging context in the exception data" do
205
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
206
- ExceptionHandling.log_error(StandardError.new("Some Exception"), "mooo", treat_like_warning: true, log_context_test: "contextual_logging")
207
-
208
- expected_log_context = {
209
- "log_context_test" => "contextual_logging"
210
- }
211
- assert_equal expected_log_context, @callback_data[:log_context]
212
- end
213
-
214
- should "support rescue exceptions from a log_error hook" do
215
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_with_failure)
216
- log_info_messages = []
217
- stub(ExceptionHandling.logger).info.with_any_args do |message, _|
218
- log_info_messages << message
219
- end
220
- assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }
221
- assert log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }
222
- end
223
-
224
- should "handle nil message exceptions resulting from the log_error hook" do
225
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_returns_nil_message_exception)
226
- log_info_messages = []
227
- stub(ExceptionHandling.logger).info.with_any_args do |message, _|
228
- log_info_messages << message
229
- end
230
- assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }
231
- assert log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }
232
- end
233
-
234
- should "handle nil message exceptions resulting from the custom data hook" do
235
- ExceptionHandling.custom_data_hook = method(:custom_data_callback_returns_nil_message_exception)
236
- log_info_messages = []
237
- stub(ExceptionHandling.logger).info.with_any_args do |message, _|
238
- log_info_messages << message
239
- end
240
- assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }
241
- assert log_info_messages.find { |message| message =~ /Unable to execute custom custom_data_hook callback/ }
242
- end
243
- end
244
-
245
- context "Exception Handling" do
246
- context "default_metric_name" do
247
- context "when metric_name is present in exception_data" do
248
- should "include metric_name in resulting metric name" do
249
- exception = StandardError.new('this is an exception')
250
- metric = ExceptionHandling.default_metric_name({ 'metric_name' => 'special_metric' }, exception, true)
251
- assert_equal 'exception_handling.special_metric', metric
252
- end
253
- end
254
-
255
- context "when metric_name is not present in exception_data" do
256
- should "return exception_handling.warning when using log warning" do
257
- warning = ExceptionHandling::Warning.new('this is a warning')
258
- metric = ExceptionHandling.default_metric_name({}, warning, false)
259
- assert_equal 'exception_handling.warning', metric
260
- end
261
-
262
- should "return exception_handling.exception when using log error" do
263
- exception = StandardError.new('this is an exception')
264
- metric = ExceptionHandling.default_metric_name({}, exception, false)
265
- assert_equal 'exception_handling.exception', metric
266
- end
267
-
268
- context "when using log error with treat_like_warning" do
269
- should "return exception_handling.unforwarded_exception when exception not present" do
270
- metric = ExceptionHandling.default_metric_name({}, nil, true)
271
- assert_equal 'exception_handling.unforwarded_exception', metric
272
- end
273
-
274
- should "return exception_handling.unforwarded_exception with exception classname when exception is present" do
275
- module SomeModule
276
- class SomeException < StandardError
277
- end
278
- end
279
-
280
- exception = SomeModule::SomeException.new('this is an exception')
281
- metric = ExceptionHandling.default_metric_name({}, exception, true)
282
- assert_equal 'exception_handling.unforwarded_exception_SomeException', metric
283
- end
284
- end
285
- end
286
- end
287
-
288
- context "default_honeybadger_metric_name" do
289
- should "return exception_handling.honeybadger.success when status is :success" do
290
- metric = ExceptionHandling.default_honeybadger_metric_name(:success)
291
- assert_equal 'exception_handling.honeybadger.success', metric
292
- end
293
-
294
- should "return exception_handling.honeybadger.failure when status is :failure" do
295
- metric = ExceptionHandling.default_honeybadger_metric_name(:failure)
296
- assert_equal 'exception_handling.honeybadger.failure', metric
297
- end
298
-
299
- should "return exception_handling.honeybadger.skipped when status is :skipped" do
300
- metric = ExceptionHandling.default_honeybadger_metric_name(:skipped)
301
- assert_equal 'exception_handling.honeybadger.skipped', metric
302
- end
303
-
304
- should "return exception_handling.honeybadger.unknown_status when status is not recognized" do
305
- metric = ExceptionHandling.default_honeybadger_metric_name(nil)
306
- assert_equal 'exception_handling.honeybadger.unknown_status', metric
307
- end
308
- end
309
-
310
- context "ExceptionHandling.ensure_safe" do
311
- should "log an exception with call stack if an exception is raised." do
312
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
313
- ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
314
- end
315
-
316
- should "log an exception with call stack if an ActionView template exception is raised." do
317
- mock(ExceptionHandling.logger).fatal(/\(Error:\d+\) ActionView::Template::Error \(blah\):\n /, anything)
318
- ExceptionHandling.ensure_safe { raise ActionView::TemplateError.new({}, ArgumentError.new("blah")) }
319
- end
320
-
321
- should "should not log an exception if an exception is not raised." do
322
- dont_allow(ExceptionHandling.logger).fatal
323
- ExceptionHandling.ensure_safe { ; }
324
- end
325
-
326
- should "return its value if used during an assignment" do
327
- dont_allow(ExceptionHandling.logger).fatal
328
- b = ExceptionHandling.ensure_safe { 5 }
329
- assert_equal 5, b
330
- end
331
-
332
- should "return nil if an exception is raised during an assignment" do
333
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
334
- b = ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
335
- assert_nil b
336
- end
337
-
338
- should "allow a message to be appended to the error when logged." do
339
- mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
340
- b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError, "blah" }
341
- assert_nil b
342
- end
343
-
344
- should "only rescue StandardError and descendents" do
345
- assert_raise(Exception) { ExceptionHandling.ensure_safe("mooo") { raise Exception } }
346
-
347
- mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
348
-
349
- b = ExceptionHandling.ensure_safe("mooo") { raise StandardError, "blah" }
350
- assert_nil b
351
- end
352
- end
353
-
354
- context "ExceptionHandling.ensure_completely_safe" do
355
- should "log an exception if an exception is raised." do
356
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
357
- ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
358
- end
359
-
360
- should "should not log an exception if an exception is not raised." do
361
- mock(ExceptionHandling.logger).fatal.times(0)
362
- ExceptionHandling.ensure_completely_safe { ; }
363
- end
364
-
365
- should "return its value if used during an assignment" do
366
- mock(ExceptionHandling.logger).fatal.times(0)
367
- b = ExceptionHandling.ensure_completely_safe { 5 }
368
- assert_equal 5, b
369
- end
370
-
371
- should "return nil if an exception is raised during an assignment" do
372
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything) { nil }
373
- b = ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
374
- assert_nil b
375
- end
376
-
377
- should "allow a message to be appended to the error when logged." do
378
- mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
379
- b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError, "blah" }
380
- assert_nil b
381
- end
382
-
383
- should "rescue any instance or child of Exception" do
384
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
385
- ExceptionHandling.ensure_completely_safe { raise Exception, "blah" }
386
- end
387
-
388
- should "not rescue the special exceptions that Ruby uses" do
389
- [SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
390
- assert_raise exception do
391
- ExceptionHandling.ensure_completely_safe do
392
- raise exception
393
- end
394
- end
395
- end
396
- end
397
- end
398
-
399
- context "ExceptionHandling.ensure_escalation" do
400
- setup do
401
- capture_notifications
402
- ActionMailer::Base.deliveries.clear
403
- end
404
-
405
- should "log the exception as usual and send the proper email" do
406
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
407
- ExceptionHandling.ensure_escalation("Favorite Feature") { raise ArgumentError, "blah" }
408
- assert_equal 1, ActionMailer::Base.deliveries.count
409
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
410
-
411
- email = ActionMailer::Base.deliveries.last
412
- assert_equal "#{ExceptionHandling.email_environment} Escalation: Favorite Feature", email.subject
413
- assert_match 'ArgumentError: blah', email.body.to_s
414
- assert_match ExceptionHandling.last_exception_timestamp.to_s, email.body.to_s
415
- end
416
-
417
- should "should not escalate if an exception is not raised." do
418
- dont_allow(ExceptionHandling.logger).fatal
419
- ExceptionHandling.ensure_escalation('Ignored') { ; }
420
- assert_equal 0, ActionMailer::Base.deliveries.count
421
- end
422
-
423
- should "log if the escalation email cannot be sent" do
424
- any_instance_of(Mail::Message) do |message|
425
- mock(message).deliver { raise RuntimeError.new, "Delivery Error" }
426
- end
427
- log_fatals = []
428
- stub(ExceptionHandling.logger) do |logger|
429
- logger.fatal.with_any_args { |*args| log_fatals << args }
430
- end
431
-
432
- ExceptionHandling.ensure_escalation("ensure context") { raise ArgumentError, "first_test_exception" }
433
-
434
- assert_match /ArgumentError.*first_test_exception/, log_fatals[0].first
435
- assert_match /safe_email_deliver.*Delivery Error/, log_fatals[1].first
436
-
437
- assert_equal 2, log_fatals.size, log_fatals.inspect
438
-
439
- assert_equal 1, sent_notifications.size, sent_notifications.inspect # still sent to honeybadger
440
- end
441
-
442
- should "allow the caller to specify custom recipients" do
443
- custom_recipients = ['something@invoca.com']
444
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
445
- ExceptionHandling.ensure_escalation("Favorite Feature", custom_recipients) { raise ArgumentError, "blah" }
446
- assert_equal 1, ActionMailer::Base.deliveries.count
447
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
448
-
449
- email = ActionMailer::Base.deliveries.last
450
- assert_equal "#{ExceptionHandling.email_environment} Escalation: Favorite Feature", email.subject
451
- assert_match 'ArgumentError: blah', email.body.to_s
452
- assert_match ExceptionHandling.last_exception_timestamp.to_s, email.body.to_s
453
- assert_equal custom_recipients, email.to
454
- end
455
- end
456
-
457
- context "ExceptionHandling.ensure_alert" do
458
- should "log the exception as usual and fire a sensu event" do
459
- mock(ExceptionHandling::Sensu).generate_event("Favorite Feature", "test context\nblah")
460
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
461
- ExceptionHandling.ensure_alert('Favorite Feature', 'test context') { raise ArgumentError, "blah" }
462
- end
463
-
464
- should "should not send sensu event if an exception is not raised." do
465
- dont_allow(ExceptionHandling.logger).fatal
466
- dont_allow(ExceptionHandling::Sensu).generate_event
467
- ExceptionHandling.ensure_alert('Ignored', 'test context') { ; }
468
- end
469
-
470
- should "log if the sensu event could not be sent" do
471
- mock(ExceptionHandling::Sensu).send_event(anything) { raise "Failed to send" }
472
- mock(ExceptionHandling.logger) do |logger|
473
- logger.fatal(/first_test_exception/, anything)
474
- logger.fatal(/Failed to send/, anything)
475
- end
476
- ExceptionHandling.ensure_alert("Not Used", 'test context') { raise ArgumentError, "first_test_exception" }
477
- end
478
-
479
- should "log if the exception message is nil" do
480
- mock(ExceptionHandling::Sensu).generate_event("some alert", "test context\n")
481
- ExceptionHandling.ensure_alert('some alert', 'test context') { raise_exception_with_nil_message }
482
- end
483
- end
484
-
485
- context "ExceptionHandling.escalate_to_production_support" do
486
- should "notify production support" do
487
- subject = "Runtime Error found!"
488
- exception = RuntimeError.new("Test")
489
- recipients = ["prodsupport@example.com"]
490
-
491
- mock(ExceptionHandling).production_support_recipients { recipients }.times(2)
492
- mock(ExceptionHandling).escalate(subject, exception, ExceptionHandling.last_exception_timestamp, recipients)
493
- ExceptionHandling.escalate_to_production_support(exception, subject)
494
- end
495
- end
496
-
497
- context "exception timestamp" do
498
- setup do
499
- Time.now_override = Time.parse('1986-5-21 4:17 am UTC')
500
- end
501
-
502
- should "include the timestamp when the exception is logged" do
503
- capture_notifications
504
-
505
- mock(ExceptionHandling.logger).fatal(/\(Error:517033020\) ArgumentError context \(blah\):\n.*exception_handling_test\.rb/, anything)
506
- b = ExceptionHandling.ensure_safe("context") { raise ArgumentError, "blah" }
507
- assert_nil b
508
-
509
- assert_equal 517_033_020, ExceptionHandling.last_exception_timestamp
510
-
511
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
512
-
513
- assert_equal 517_033_020, sent_notifications.last.enhanced_data['timestamp']
514
- end
515
- end
516
-
517
- should "log the error if the exception message is nil" do
518
- capture_notifications
519
-
520
- ExceptionHandling.log_error(exception_with_nil_message)
521
-
522
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
523
- assert_equal 'RuntimeError: ', sent_notifications.last.enhanced_data['error_string']
524
- end
525
-
526
- should "log the error if the exception message is nil and the exception context is a hash" do
527
- capture_notifications
528
-
529
- ExceptionHandling.log_error(exception_with_nil_message, "SERVER_NAME" => "exceptional.com")
530
-
531
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
532
- assert_equal 'RuntimeError: ', sent_notifications.last.enhanced_data['error_string']
533
- end
534
-
535
- context "Honeybadger integration" do
536
- context "with Honeybadger not defined" do
537
- setup do
538
- stub(ExceptionHandling).honeybadger_defined? { false }
539
- end
540
-
541
- should "not invoke send_exception_to_honeybadger when log_error is executed" do
542
- dont_allow(ExceptionHandling).send_exception_to_honeybadger
543
- ExceptionHandling.log_error(exception_1)
544
- end
545
-
546
- should "not invoke send_exception_to_honeybadger when ensure_safe is executed" do
547
- dont_allow(ExceptionHandling).send_exception_to_honeybadger
548
- ExceptionHandling.ensure_safe { raise exception_1 }
549
- end
550
- end
551
-
552
- context "with Honeybadger defined" do
553
- teardown do
554
- ExceptionHandling.current_controller = nil
555
- end
556
-
557
- should "not send_exception_to_honeybadger when log_warning is executed" do
558
- dont_allow(ExceptionHandling).send_exception_to_honeybadger
559
- ExceptionHandling.log_warning("This should not go to honeybadger")
560
- end
561
-
562
- should "not send_exception_to_honeybadger when log_error is called with a Warning" do
563
- dont_allow(ExceptionHandling).send_exception_to_honeybadger
564
- ExceptionHandling.log_error(ExceptionHandling::Warning.new("This should not go to honeybadger"))
565
- end
566
-
567
- should "invoke send_exception_to_honeybadger when log_error is executed" do
568
- mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args
569
- ExceptionHandling.log_error(exception_1)
570
- end
571
-
572
- should "invoke send_exception_to_honeybadger when log_error_rack is executed" do
573
- mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args
574
- ExceptionHandling.log_error_rack(exception_1, {}, nil)
575
- end
576
-
577
- should "invoke send_exception_to_honeybadger when ensure_safe is executed" do
578
- mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args
579
- ExceptionHandling.ensure_safe { raise exception_1 }
580
- end
581
-
582
- should "specify error message as an empty string when notifying honeybadger if exception message is nil" do
583
- mock(Honeybadger).notify.with_any_args do |args|
584
- assert_equal "", args[:error_message]
585
- end
586
- ExceptionHandling.log_error(exception_with_nil_message)
587
- end
588
-
589
- should "send error details and relevant context data to Honeybadger" do
590
- Time.now_override = Time.now
591
- env = { server: "fe98" }
592
- parameters = { advertiser_id: 435, controller: "some_controller" }
593
- session = { username: "jsmith" }
594
- request_uri = "host/path"
595
- ExceptionHandling.current_controller = create_dummy_controller(env, parameters, session, request_uri)
596
- stub(ExceptionHandling).server_name { "invoca_fe98" }
597
-
598
- exception = StandardError.new("Some Exception")
599
- exception.set_backtrace([
600
- "test/unit/exception_handling_test.rb:847:in `exception_1'",
601
- "test/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
602
- ])
603
- exception_context = { "SERVER_NAME" => "exceptional.com" }
604
-
605
- honeybadger_data = nil
606
- mock(Honeybadger).notify.with_any_args do |data|
607
- honeybadger_data = data
608
- end
609
- ExceptionHandling.log_error(exception, exception_context) do |data|
610
- data[:scm_revision] = "5b24eac37aaa91f5784901e9aabcead36fd9df82"
611
- data[:user_details] = { username: "jsmith" }
612
- data[:event_response] = "Event successfully received"
613
- data[:other_section] = "This should not be included in the response"
614
- end
615
-
616
- expected_data = {
617
- error_class: :"Test Exception",
618
- error_message: "Some Exception",
619
- controller: "some_controller",
620
- exception: exception,
621
- context: {
622
- timestamp: Time.now.to_i,
623
- error_class: "StandardError",
624
- server: "invoca_fe98",
625
- exception_context: { "SERVER_NAME" => "exceptional.com" },
626
- scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
627
- notes: "this is used by a test",
628
- user_details: { "username" => "jsmith" },
629
- request: {
630
- "params" => { "advertiser_id" => 435, "controller" => "some_controller" },
631
- "rails_root" => "Rails.root not defined. Is this a test environment?",
632
- "url" => "host/path"
633
- },
634
- session: {
635
- "key" => nil,
636
- "data" => { "username" => "jsmith" }
637
- },
638
- environment: {
639
- "SERVER_NAME" => "exceptional.com"
640
- },
641
- backtrace: [
642
- "test/unit/exception_handling_test.rb:847:in `exception_1'",
643
- "test/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
644
- ],
645
- event_response: "Event successfully received"
646
- }
647
- }
648
- assert_equal_with_diff expected_data, honeybadger_data
649
- end
650
-
651
- context "with post_log_error_hook set" do
652
- teardown do
653
- ExceptionHandling.post_log_error_hook = nil
654
- end
655
-
656
- should "not send notification to honeybadger when exception description has the flag turned off and call log error callback with logged_to_honeybadger set to nil" do
657
- @fail_count = 0
658
- @honeybadger_status = nil
659
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
660
- filter_list = {
661
- NoHoneybadger: {
662
- error: "suppress Honeybadger notification",
663
- send_to_honeybadger: false
664
- }
665
- }
666
- stub(File).mtime { incrementing_mtime }
667
- mock(YAML).load_file.with_any_args { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }.at_least(1)
668
-
669
- mock.proxy(ExceptionHandling).send_exception_to_honeybadger_unless_filtered.with_any_args.once
670
- dont_allow(Honeybadger).notify
671
- ExceptionHandling.log_error(StandardError.new("suppress Honeybadger notification"))
672
- assert_equal :skipped, @honeybadger_status
673
- end
674
-
675
- should "call log error callback with logged_to_honeybadger set to false if an error occurs while attempting to notify honeybadger" do
676
- @fail_count = 0
677
- @honeybadger_status = nil
678
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
679
- mock(Honeybadger).notify.with_any_args { raise "Honeybadger Notification Failure" }
680
- ExceptionHandling.log_error(exception_1)
681
- assert_equal :failure, @honeybadger_status
682
- end
683
-
684
- should "call log error callback with logged_to_honeybadger set to false on unsuccessful honeybadger notification" do
685
- @fail_count = 0
686
- @honeybadger_status = nil
687
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
688
- mock(Honeybadger).notify.with_any_args { false }
689
- ExceptionHandling.log_error(exception_1)
690
- assert_equal :failure, @honeybadger_status
691
- end
692
-
693
- should "call log error callback with logged_to_honeybadger set to true on successful honeybadger notification" do
694
- @fail_count = 0
695
- @honeybadger_status = nil
696
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
697
- mock(Honeybadger).notify.with_any_args { '06220c5a-b471-41e5-baeb-de247da45a56' }
698
- ExceptionHandling.log_error(exception_1)
699
- assert_equal :success, @honeybadger_status
700
- end
701
- end
702
- end
703
- end
704
-
705
- class EventResponse
706
- def to_s
707
- "message from to_s!"
708
- end
709
- end
710
-
711
- should "allow sections to have data with just a to_s method" do
712
- capture_notifications
713
-
714
- ExceptionHandling.log_error("This is my RingSwitch example.") do |data|
715
- data.merge!(event_response: EventResponse.new)
716
- end
717
-
718
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
719
- assert_match(/message from to_s!/, sent_notifications.last.enhanced_data['event_response'].to_s)
720
- end
721
- end
722
-
723
- should "return the error ID (timestamp)" do
724
- result = ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
725
- assert_equal ExceptionHandling.last_exception_timestamp, result
726
- end
727
-
728
- should "rescue exceptions that happen in log_error" do
729
- stub(ExceptionHandling).make_exception { raise ArgumentError, "Bad argument" }
730
- mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
731
- satisfy { |context| context['ExceptionHandlingError: log_error rescued exception while logging Runtime message'] },
732
- anything)
733
- ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
734
- end
735
-
736
- should "rescue exceptions that happen when log_error yields" do
737
- mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
738
- satisfy { |context| context['Context message'] },
739
- anything,
740
- anything)
741
- ExceptionHandling.log_error(ArgumentError.new("Bad argument"), "Context message") { |_data| raise 'Error!!!' }
742
- end
743
-
744
- context "Exception Filtering" do
745
- setup do
746
- filter_list = { exception1: { 'error' => "my error message" },
747
- exception2: { 'error' => "some other message", :session => "misc data" } }
748
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
749
-
750
- # bump modified time up to get the above filter loaded
751
- stub(File).mtime { incrementing_mtime }
752
- end
753
-
754
- should "handle case where filter list is not found" do
755
- stub(YAML).load_file { raise Errno::ENOENT, "File not found" }
756
-
757
- capture_notifications
758
-
759
- ExceptionHandling.log_error("My error message is in list")
760
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
761
- end
762
-
763
- should "log exception and suppress email when exception is on filter list" do
764
- capture_notifications
765
-
766
- ExceptionHandling.log_error("Error message is not in list")
767
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
768
-
769
- sent_notifications.clear
770
- ExceptionHandling.log_error("My error message is in list")
771
- assert_equal 0, sent_notifications.size, sent_notifications.inspect
772
- end
773
-
774
- should "allow filtering exception on any text in exception data" do
775
- filters = { exception1: { session: "data: my extra session data" } }
776
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filters) }
777
-
778
- capture_notifications
779
-
780
- ExceptionHandling.log_error("No match here") do |data|
781
- data[:session] = {
782
- key: "@session_id",
783
- data: "my extra session data"
784
- }
785
- end
786
- assert_equal 0, sent_notifications.size, sent_notifications.inspect
787
-
788
- ExceptionHandling.log_error("No match here") do |data|
789
- data[:session] = {
790
- key: "@session_id",
791
- data: "my extra session <no match!> data"
792
- }
793
- end
794
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
795
- end
796
-
797
- should "reload filter list on the next exception if file was modified" do
798
- capture_notifications
799
-
800
- ExceptionHandling.log_error("Error message is not in list")
801
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
802
-
803
- filter_list = { exception1: { 'error' => "Error message is not in list" } }
804
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
805
- stub(File).mtime { incrementing_mtime }
806
-
807
- sent_notifications.clear
808
- ExceptionHandling.log_error("Error message is not in list")
809
- assert_equal 0, sent_notifications.size, sent_notifications.inspect
810
- end
811
-
812
- should "not consider filter if both error message and body do not match" do
813
- capture_notifications
814
-
815
- # error message matches, but not full text
816
- ExceptionHandling.log_error("some other message")
817
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
818
-
819
- # now both match
820
- sent_notifications.clear
821
- ExceptionHandling.log_error("some other message") do |data|
822
- data[:session] = { some_random_key: "misc data" }
823
- end
824
- assert_equal 0, sent_notifications.size, sent_notifications.inspect
825
- end
826
-
827
- should "skip environment keys not on whitelist" do
828
- capture_notifications
829
-
830
- ExceptionHandling.log_error("some message") do |data|
831
- data[:environment] = { SERVER_PROTOCOL: "HTTP/1.0", RAILS_SECRETS_YML_CONTENTS: 'password: VERY_SECRET_PASSWORD' }
832
- end
833
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
834
-
835
- mail = sent_notifications.last
836
- environment = mail.enhanced_data['environment']
837
-
838
- assert_nil environment["RAILS_SECRETS_YML_CONTENTS"], environment.inspect # this is not on whitelist
839
- assert environment["SERVER_PROTOCOL"], environment.inspect # this is
840
- end
841
-
842
- should "omit environment defaults" do
843
- capture_notifications
844
-
845
- stub(ExceptionHandling).send_exception_to_honeybadger(anything) { |exception_info| sent_notifications << exception_info }
846
-
847
- ExceptionHandling.log_error("some message") do |data|
848
- data[:environment] = { SERVER_PORT: '80', SERVER_PROTOCOL: "HTTP/1.0" }
849
- end
850
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
851
- mail = sent_notifications.last
852
- environment = mail.enhanced_data['environment']
853
-
854
- assert_nil environment["SERVER_PORT"], environment.inspect # this was default
855
- assert environment["SERVER_PROTOCOL"], environment # this was not
856
- end
857
-
858
- should "reject the filter file if any contain all empty regexes" do
859
- filter_list = { exception1: { 'error' => "", :session => "" },
860
- exception2: { 'error' => "is not in list", :session => "" } }
861
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
862
- stub(File).mtime { incrementing_mtime }
863
-
864
- capture_notifications
865
-
866
- ExceptionHandling.log_error("Error message is not in list")
867
- assert_equal 1, sent_notifications.size, sent_notifications.inspect
868
- end
869
-
870
- should "reload filter file if filename changes" do
871
- catalog = ExceptionHandling.exception_catalog
872
- ExceptionHandling.filter_list_filename = "./config/other_exception_filters.yml"
873
- assert_not_equal catalog, ExceptionHandling.exception_catalog
874
- end
875
-
876
- context "Exception Handling Mailer" do
877
- EXPECTED_SMTP_HASH =
878
- {
879
- host: '127.0.0.1',
880
- domain: 'localhost.localdomain',
881
- from: 'server@example.com',
882
- to: 'escalation@example.com'
883
- }.freeze
884
-
885
- [[true, false], [true, true]].each do |em_flag, synchrony_flag|
886
- context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
887
- setup do
888
- ExceptionHandling.eventmachine_safe = em_flag
889
- ExceptionHandling.eventmachine_synchrony = synchrony_flag
890
- EventMachineStub.block = nil
891
- set_test_const('EventMachine', EventMachineStub)
892
- set_test_const('EventMachine::Protocols', Module.new)
893
- set_test_const('EventMachine::DNS', Module.new)
894
- set_test_const('EventMachine::DNS::Resolver', DNSResolvStub)
895
- end
896
-
897
- teardown do
898
- ExceptionHandling.eventmachine_safe = false
899
- ExceptionHandling.eventmachine_synchrony = false
900
- end
901
-
902
- should "schedule EventMachine STMP when EventMachine defined" do
903
- ActionMailer::Base.deliveries.clear
904
-
905
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
906
-
907
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
908
- assert EventMachineStub.block
909
- EventMachineStub.block.call
910
- assert DNSResolvStub.callback_block
911
- DNSResolvStub.callback_block.call ['127.0.0.1']
912
- assert_equal_with_diff EXPECTED_SMTP_HASH, (SmtpClientStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }, SmtpClientStub.send_hash.inspect
913
- assert_equal((synchrony_flag ? :asend : :send), SmtpClientStub.last_method)
914
- assert_match(/Exception to escalate/, SmtpClientStub.send_hash[:content])
915
- assert_emails 0, ActionMailer::Base.deliveries.*.to_s
916
- end
917
-
918
- should "pass the content as a proper rfc 2822 message" do
919
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
920
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
921
- assert EventMachineStub.block
922
- EventMachineStub.block.call
923
- assert DNSResolvStub.callback_block
924
- DNSResolvStub.callback_block.call ['127.0.0.1']
925
- assert content = SmtpClientStub.send_hash[:content]
926
- assert_match(/Content-Transfer-Encoding: 7bit/, content)
927
- assert_match(/\r\n\.\r\n\z/, content)
928
- end
929
-
930
- should "log fatal on EventMachine STMP errback" do
931
- ActionMailer::Base.deliveries.clear
932
-
933
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
934
- mock(ExceptionHandling.logger).fatal(/Exception to escalate/, anything)
935
- mock(ExceptionHandling.logger).fatal(/Failed to email by SMTP: "credential mismatch"/)
936
-
937
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
938
- assert EventMachineStub.block
939
- EventMachineStub.block.call
940
- assert DNSResolvStub.callback_block
941
- DNSResolvStub.callback_block.call(['127.0.0.1'])
942
- SmtpClientErrbackStub.block.call("credential mismatch")
943
- assert_equal_with_diff EXPECTED_SMTP_HASH, (SmtpClientErrbackStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }, SmtpClientErrbackStub.send_hash.inspect
944
- end
945
-
946
- should "log fatal on EventMachine dns resolver errback" do
947
- mock(ExceptionHandling.logger).fatal(/Exception to escalate/, anything)
948
- mock(ExceptionHandling.logger).fatal(/Failed to resolv DNS for localhost: "softlayer sucks"/)
949
-
950
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
951
- assert EventMachineStub.block
952
- EventMachineStub.block.call
953
- DNSResolvStub.errback_block.call("softlayer sucks")
954
- end
955
- end
956
- end
957
- end
958
- end
959
-
960
- context "Exception mapping" do
961
- setup do
962
- @data = {
963
- environment: {
964
- 'HTTP_HOST' => "localhost",
965
- 'HTTP_REFERER' => "http://localhost/action/controller/instance",
966
- },
967
- session: {
968
- data: {
969
- affiliate_id: defined?(Affiliate) ? Affiliate.first.id : '1',
970
- edit_mode: true,
971
- advertiser_id: defined?(Advertiser) ? Advertiser.first.id : '1',
972
- username_id: defined?(Username) ? Username.first.id : '1',
973
- user_id: defined?(User) ? User.first.id : '1',
974
- flash: {},
975
- impersonated_organization_pk: 'Advertiser_1'
976
- }
977
- },
978
- request: {},
979
- backtrace: ["[GEM_ROOT]/gems/actionpack-2.1.0/lib/action_controller/filters.rb:580:in `call_filters'", "[GEM_ROOT]/gems/actionpack-2.1.0/lib/action_controller/filters.rb:601:in `run_before_filters'"],
980
- api_key: "none",
981
- error_class: "StandardError",
982
- error: 'Some error message'
983
- }
984
- end
985
-
986
- should "clean backtraces" do
987
- begin
988
- raise "test exception"
989
- rescue => ex
990
- backtrace = ex.backtrace
991
- end
992
- result = ExceptionHandling.send(:clean_backtrace, ex).to_s
993
- assert_not_equal result, backtrace
994
- end
995
-
996
- should "return entire backtrace if cleaned is emtpy" do
997
- begin
998
- backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
999
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
1000
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `__send__'",
1001
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `find'",
1002
- "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `call'",
1003
- "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `test: Exception mapping should return entire backtrace if cleaned is emtpy. '",
1004
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `__send__'",
1005
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `run'",
1006
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:447:in `_run__1913317170__setup__4__callbacks'",
1007
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `send'",
1008
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `__run_callback'",
1009
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:385:in `_run_setup_callbacks'",
1010
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `send'",
1011
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'",
1012
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:70:in `run'",
1013
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'",
1014
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'",
1015
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'",
1016
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `old_run_suite'",
1017
- "(eval):12:in `run_suite'",
1018
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `send'",
1019
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `start_mediator'",
1020
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:81:in `start'",
1021
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'",
1022
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'",
1023
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:279",
1024
- "-e:1"]
1025
-
1026
- module ::Rails
1027
- class BacktraceCleaner
1028
- def clean(_backtrace)
1029
- []
1030
- end
1031
- end
1032
- end
1033
-
1034
- mock(Rails).backtrace_cleaner { Rails::BacktraceCleaner.new }
1035
-
1036
- ex = Exception.new
1037
- ex.set_backtrace(backtrace)
1038
- result = ExceptionHandling.send(:clean_backtrace, ex)
1039
- assert_equal backtrace, result
1040
- ensure
1041
- Object.send(:remove_const, :Rails)
1042
- end
1043
- end
1044
- end
1045
-
1046
- context "log_perodically" do
1047
- setup do
1048
- Time.now_override = Time.now # Freeze time
1049
- ExceptionHandling.logger.clear
1050
- end
1051
-
1052
- teardown do
1053
- Time.now_override = nil
1054
- end
1055
-
1056
- should "take in additional logging context and pass them to the logger" do
1057
- ExceptionHandling.log_periodically(:test_context_with_periodic, 30.minutes, "this will be written", service_name: 'exception_handling')
1058
- assert_not_empty logged_excluding_reload_filter.last[:context]
1059
- assert_equal({ service_name: 'exception_handling' }, logged_excluding_reload_filter.last[:context])
1060
- end
1061
-
1062
- should "log immediately when we are expected to log" do
1063
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
1064
- assert_equal 1, logged_excluding_reload_filter.size
1065
-
1066
- Time.now_override = Time.now + 5.minutes
1067
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
1068
- assert_equal 1, logged_excluding_reload_filter.size
1069
-
1070
- ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
1071
- assert_equal 2, logged_excluding_reload_filter.size
1072
-
1073
- Time.now_override = Time.now + 26.minutes
1074
-
1075
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
1076
- assert_equal 3, logged_excluding_reload_filter.size
1077
- end
1078
- end
1079
- end
1080
-
1081
- private
1082
-
1083
- def logged_excluding_reload_filter
1084
- ExceptionHandling.logger.logged.select { |l| l[:message] !~ /Reloading filter list/ }
1085
- end
1086
-
1087
- def incrementing_mtime
1088
- @mtime ||= Time.now
1089
- @mtime += 1.day
1090
- end
1091
-
1092
- def exception_1
1093
- @exception_1 ||=
1094
- begin
1095
- raise StandardError, "Exception 1"
1096
- rescue => ex
1097
- ex
1098
- end
1099
- end
1100
-
1101
- def exception_2
1102
- @exception_2 ||=
1103
- begin
1104
- raise StandardError, "Exception 2"
1105
- rescue => ex
1106
- ex
1107
- end
1108
- end
1109
- end