exception_handling 2.11.3 → 3.0.pre.1

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