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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/workflows/pipeline.yml +36 -0
  4. data/.gitignore +3 -0
  5. data/.rspec +3 -0
  6. data/.ruby-version +1 -1
  7. data/.tool-versions +1 -0
  8. data/Appraisals +13 -0
  9. data/CHANGELOG.md +150 -0
  10. data/Gemfile +10 -16
  11. data/Gemfile.lock +65 -128
  12. data/README.md +51 -19
  13. data/Rakefile +8 -11
  14. data/exception_handling.gemspec +11 -13
  15. data/gemfiles/rails_5.gemfile +16 -0
  16. data/gemfiles/rails_6.gemfile +16 -0
  17. data/gemfiles/rails_7.gemfile +16 -0
  18. data/lib/exception_handling/escalate_callback.rb +19 -0
  19. data/lib/exception_handling/exception_info.rb +15 -11
  20. data/lib/exception_handling/log_stub_error.rb +2 -1
  21. data/lib/exception_handling/logging_methods.rb +21 -0
  22. data/lib/exception_handling/testing.rb +9 -12
  23. data/lib/exception_handling/version.rb +1 -1
  24. data/lib/exception_handling.rb +83 -173
  25. data/{test → spec}/helpers/exception_helpers.rb +2 -2
  26. data/spec/rake_test_warning_false.rb +20 -0
  27. data/{test/test_helper.rb → spec/spec_helper.rb} +63 -66
  28. data/spec/unit/exception_handling/escalate_callback_spec.rb +81 -0
  29. data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
  30. data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
  31. data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +170 -114
  32. data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
  33. data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
  34. data/spec/unit/exception_handling_spec.rb +1063 -0
  35. metadata +60 -89
  36. data/lib/exception_handling/honeybadger_callbacks.rb +0 -59
  37. data/lib/exception_handling/mailer.rb +0 -70
  38. data/lib/exception_handling/methods.rb +0 -101
  39. data/lib/exception_handling/sensu.rb +0 -28
  40. data/semaphore_ci/setup.sh +0 -3
  41. data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
  42. data/test/unit/exception_handling/exception_description_test.rb +0 -82
  43. data/test/unit/exception_handling/honeybadger_callbacks_test.rb +0 -122
  44. data/test/unit/exception_handling/mailer_test.rb +0 -98
  45. data/test/unit/exception_handling/methods_test.rb +0 -84
  46. data/test/unit/exception_handling/sensu_test.rb +0 -52
  47. data/test/unit/exception_handling_test.rb +0 -1109
  48. data/views/exception_handling/mailer/escalate_custom.html.erb +0 -17
  49. data/views/exception_handling/mailer/escalation_notification.html.erb +0 -17
  50. data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +0 -82
  51. /data/{test → spec}/helpers/controller_helpers.rb +0 -0
@@ -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