exception_handling 2.13.0 → 3.0.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +33 -68
  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 -119
  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 -1303
@@ -1,1303 +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 Gem::Version.new(RUBY_VERSION) < Gem::Version.new('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 "with include_prefix true" do
311
- it "logs a deprecation warning" do
312
- expect { ExceptionHandling.default_metric_name({}, StandardError.new('this is an exception'), false) }
313
- .to output(/DEPRECATION WARNING: the 'expection_handling\.' prefix in ExceptionHandling::default_metric_name is deprecated/).to_stderr
314
- end
315
-
316
- context "when metric_name is present in exception_data" do
317
- it "include metric_name in resulting metric name" do
318
- exception = StandardError.new('this is an exception')
319
- metric = ExceptionHandling.default_metric_name({ 'metric_name' => 'special_metric' }, exception, true, include_prefix: true)
320
- expect(metric).to eq('exception_handling.special_metric')
321
- end
322
- end
323
-
324
- context "when metric_name is not present in exception_data" do
325
- it "return exception_handling.warning when using log warning" do
326
- warning = ExceptionHandling::Warning.new('this is a warning')
327
- metric = ExceptionHandling.default_metric_name({}, warning, false, include_prefix: true)
328
- expect(metric).to eq('exception_handling.warning')
329
- end
330
-
331
- it "return exception_handling.exception when using log error" do
332
- exception = StandardError.new('this is an exception')
333
- metric = ExceptionHandling.default_metric_name({}, exception, false, include_prefix: true)
334
- expect(metric).to eq('exception_handling.exception')
335
- end
336
-
337
- context "when using log error with treat_like_warning" do
338
- it "return exception_handling.unforwarded_exception when exception not present" do
339
- metric = ExceptionHandling.default_metric_name({}, nil, true, include_prefix: true)
340
- expect(metric).to eq('exception_handling.unforwarded_exception')
341
- end
342
-
343
- it "return exception_handling.unforwarded_exception with exception classname when exception is present" do
344
- module SomeModule
345
- class SomeException < StandardError
346
- end
347
- end
348
-
349
- exception = SomeModule::SomeException.new('this is an exception')
350
- metric = ExceptionHandling.default_metric_name({}, exception, true, include_prefix: true)
351
- expect(metric).to eq('exception_handling.unforwarded_exception_SomeException')
352
- end
353
- end
354
- end
355
- end
356
-
357
- context "with include_prefix false" do
358
- context "when metric_name is present in exception_data" do
359
- it "include metric_name in resulting metric name" do
360
- exception = StandardError.new('this is an exception')
361
- metric = ExceptionHandling.default_metric_name({ 'metric_name' => 'special_metric' }, exception, true, include_prefix: false)
362
- expect(metric).to eq('special_metric')
363
- end
364
- end
365
-
366
- context "when metric_name is not present in exception_data" do
367
- it "return exception_handling.warning when using log warning" do
368
- warning = ExceptionHandling::Warning.new('this is a warning')
369
- metric = ExceptionHandling.default_metric_name({}, warning, false, include_prefix: false)
370
- expect(metric).to eq('warning')
371
- end
372
-
373
- it "return exception_handling.exception when using log error" do
374
- exception = StandardError.new('this is an exception')
375
- metric = ExceptionHandling.default_metric_name({}, exception, false, include_prefix: false)
376
- expect(metric).to eq('exception')
377
- end
378
-
379
- context "when using log error with treat_like_warning" do
380
- it "return exception_handling.unforwarded_exception when exception not present" do
381
- metric = ExceptionHandling.default_metric_name({}, nil, true, include_prefix: false)
382
- expect(metric).to eq('unforwarded_exception')
383
- end
384
-
385
- it "return exception_handling.unforwarded_exception with exception classname when exception is present" do
386
- module SomeModule
387
- class SomeException < StandardError
388
- end
389
- end
390
-
391
- exception = SomeModule::SomeException.new('this is an exception')
392
- metric = ExceptionHandling.default_metric_name({}, exception, true, include_prefix: false)
393
- expect(metric).to eq('unforwarded_exception_SomeException')
394
- end
395
- end
396
- end
397
- end
398
- end
399
-
400
- context "default_honeybadger_metric_name" do
401
- it "return exception_handling.honeybadger.success when status is :success" do
402
- metric = ExceptionHandling.default_honeybadger_metric_name(:success)
403
- expect(metric).to eq('exception_handling.honeybadger.success')
404
- end
405
-
406
- it "return exception_handling.honeybadger.failure when status is :failure" do
407
- metric = ExceptionHandling.default_honeybadger_metric_name(:failure)
408
- expect(metric).to eq('exception_handling.honeybadger.failure')
409
- end
410
-
411
- it "return exception_handling.honeybadger.skipped when status is :skipped" do
412
- metric = ExceptionHandling.default_honeybadger_metric_name(:skipped)
413
- expect(metric).to eq('exception_handling.honeybadger.skipped')
414
- end
415
-
416
- it "return exception_handling.honeybadger.unknown_status when status is not recognized" do
417
- metric = ExceptionHandling.default_honeybadger_metric_name(nil)
418
- expect(metric).to eq('exception_handling.honeybadger.unknown_status')
419
- end
420
- end
421
-
422
- context "ExceptionHandling.ensure_safe" do
423
- it "log an exception with call stack if an exception is raised." do
424
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
425
- ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
426
- end
427
-
428
- if ActionView::VERSION::MAJOR >= 5
429
- it "log an exception with call stack if an ActionView template exception is raised." do
430
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:\d+\) \nActionView::Template::Error: \(blah\):\n /, any_args)
431
- ExceptionHandling.ensure_safe do
432
- begin
433
- # Rails 5 made the switch from ActionView::TemplateError taking in the original exception
434
- # as an argument to using the $! global to extract the original exception
435
- raise ArgumentError, "blah"
436
- rescue
437
- raise ActionView::TemplateError.new({})
438
- end
439
- end
440
- end
441
- else
442
- it "log an exception with call stack if an ActionView template exception is raised." do
443
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:\d+\) \nActionView::Template::Error: \(blah\):\n /, any_args)
444
- ExceptionHandling.ensure_safe { raise ActionView::TemplateError.new({}, ArgumentError.new("blah")) }
445
- end
446
- end
447
-
448
- it "should not log an exception if an exception is not raised." do
449
- expect(ExceptionHandling.logger).to_not receive(:fatal)
450
- ExceptionHandling.ensure_safe { ; }
451
- end
452
-
453
- it "return its value if used during an assignment" do
454
- expect(ExceptionHandling.logger).to_not receive(:fatal)
455
- b = ExceptionHandling.ensure_safe { 5 }
456
- expect(b).to eq(5)
457
- end
458
-
459
- it "return nil if an exception is raised during an assignment" do
460
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
461
- b = ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
462
- expect(b).to be_nil
463
- end
464
-
465
- it "allow a message to be appended to the error when logged." do
466
- expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, any_args)
467
- b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError, "blah" }
468
- expect(b).to be_nil
469
- end
470
-
471
- it "only rescue StandardError and descendents" do
472
- expect { ExceptionHandling.ensure_safe("mooo") { raise Exception } }.to raise_exception(Exception)
473
-
474
- expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nStandardError: \(blah\):\n.*exception_handling_spec\.rb/, any_args)
475
-
476
- b = ExceptionHandling.ensure_safe("mooo") { raise StandardError, "blah" }
477
- expect(b).to be_nil
478
- end
479
- end
480
-
481
- context "ExceptionHandling.ensure_completely_safe" do
482
- it "log an exception if an exception is raised." do
483
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
484
- ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
485
- end
486
-
487
- it "should not log an exception if an exception is not raised." do
488
- expect(ExceptionHandling.logger).to receive(:fatal).exactly(0)
489
- ExceptionHandling.ensure_completely_safe { ; }
490
- end
491
-
492
- it "return its value if used during an assignment" do
493
- expect(ExceptionHandling.logger).to receive(:fatal).exactly(0)
494
- b = ExceptionHandling.ensure_completely_safe { 5 }
495
- expect(b).to eq(5)
496
- end
497
-
498
- it "return nil if an exception is raised during an assignment" do
499
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args) { nil }
500
- b = ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
501
- expect(b).to be_nil
502
- end
503
-
504
- it "allow a message to be appended to the error when logged." do
505
- expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, any_args)
506
- b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError, "blah" }
507
- expect(b).to be_nil
508
- end
509
-
510
- it "rescue any instance or child of Exception" do
511
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
512
- ExceptionHandling.ensure_completely_safe { raise Exception, "blah" }
513
- end
514
-
515
- it "not rescue the special exceptions that Ruby uses" do
516
- [SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
517
- expect do
518
- ExceptionHandling.ensure_completely_safe do
519
- raise exception
520
- end
521
- end.to raise_exception(exception)
522
- end
523
- end
524
- end
525
-
526
- context "ExceptionHandling.ensure_escalation" do
527
- before do
528
- capture_notifications
529
- ActionMailer::Base.deliveries.clear
530
- end
531
-
532
- it "log the exception as usual and send the proper email" do
533
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
534
- ExceptionHandling.ensure_escalation("Favorite Feature") { raise ArgumentError, "blah" }
535
- expect(ActionMailer::Base.deliveries.count).to eq(1)
536
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
537
-
538
- email = ActionMailer::Base.deliveries.last
539
- expect(email.subject).to eq("#{ExceptionHandling.email_environment} Escalation: Favorite Feature")
540
- expect(email.body.to_s).to match('ArgumentError: blah')
541
- expect(email.body.to_s).to match(ExceptionHandling.last_exception_timestamp.to_s)
542
- end
543
-
544
- it "should not escalate if an exception is not raised." do
545
- expect(ExceptionHandling.logger).to_not receive(:fatal)
546
- ExceptionHandling.ensure_escalation('Ignored') { ; }
547
- expect(ActionMailer::Base.deliveries.count).to eq(0)
548
- end
549
-
550
- it "log if the escalation email cannot be sent" do
551
- expect_any_instance_of(Mail::Message).to receive(:deliver).and_raise(RuntimeError.new, "Delivery Error")
552
- log_fatals = []
553
- expect(ExceptionHandling.logger).to receive(:fatal).with(any_args).at_least(:once) do |*args|
554
- log_fatals << args
555
- end
556
-
557
- ExceptionHandling.ensure_escalation("ensure context") { raise ArgumentError, "first_test_exception" }
558
- expect(log_fatals[0].first).to match(/ArgumentError.*first_test_exception/)
559
- expect(log_fatals[1].first).to match(/safe_email_deliver.*Delivery Error/m)
560
-
561
- expect(log_fatals.size).to eq(2), log_fatals.inspect
562
-
563
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect # still sent to honeybadger
564
- end
565
-
566
- it "allow the caller to specify custom recipients" do
567
- custom_recipients = ['something@invoca.com']
568
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
569
- ExceptionHandling.ensure_escalation("Favorite Feature", custom_recipients) { raise ArgumentError, "blah" }
570
- expect(ActionMailer::Base.deliveries.count).to eq(1)
571
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
572
-
573
- email = ActionMailer::Base.deliveries.last
574
- expect(email.subject).to eq("#{ExceptionHandling.email_environment} Escalation: Favorite Feature")
575
- expect(email.body.to_s).to match('ArgumentError: blah')
576
- expect(email.body.to_s).to match(ExceptionHandling.last_exception_timestamp.to_s)
577
- expect(email.to).to eq(custom_recipients)
578
- end
579
- end
580
-
581
- context "ExceptionHandling.ensure_alert" do
582
- it "log the exception as usual and fire a sensu event" do
583
- expect(ExceptionHandling::Sensu).to receive(:generate_event).with("Favorite Feature", "test context\nblah")
584
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, any_args)
585
- ExceptionHandling.ensure_alert('Favorite Feature', 'test context') { raise ArgumentError, "blah" }
586
- end
587
-
588
- it "should not send sensu event if an exception is not raised." do
589
- expect(ExceptionHandling.logger).to_not receive(:fatal)
590
- expect(ExceptionHandling::Sensu).to_not receive(:generate_event)
591
- ExceptionHandling.ensure_alert('Ignored', 'test context') { ; }
592
- end
593
-
594
- it "log if the sensu event could not be sent" do
595
- expect(ExceptionHandling::Sensu).to receive(:send_event).with(anything) { raise "Failed to send" }
596
- expect(ExceptionHandling.logger).to receive(:fatal).with(/first_test_exception/, any_args)
597
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to send/, any_args)
598
- ExceptionHandling.ensure_alert("Not Used", 'test context') { raise ArgumentError, "first_test_exception" }
599
- end
600
-
601
- it "log if the exception message is nil" do
602
- expect(ExceptionHandling::Sensu).to receive(:generate_event).with("some alert", "test context\n")
603
- ExceptionHandling.ensure_alert('some alert', 'test context') { raise_exception_with_nil_message }
604
- end
605
- end
606
-
607
- context "ExceptionHandling.escalate_to_production_support" do
608
- it "notify production support" do
609
- subject = "Runtime Error found!"
610
- exception = RuntimeError.new("Test")
611
- recipients = ["prodsupport@example.com"]
612
-
613
- expect(ExceptionHandling).to receive(:production_support_recipients).and_return(recipients).exactly(2)
614
- expect(ExceptionHandling).to receive(:escalate).with(subject, exception, ExceptionHandling.last_exception_timestamp, recipients)
615
- ExceptionHandling.escalate_to_production_support(exception, subject)
616
- end
617
- end
618
-
619
- context "exception timestamp" do
620
- before do
621
- Time.now_override = Time.parse('1986-5-21 4:17 am UTC')
622
- end
623
-
624
- it "include the timestamp when the exception is logged" do
625
- capture_notifications
626
-
627
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:517033020\) context\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, any_args)
628
- b = ExceptionHandling.ensure_safe("context") { raise ArgumentError, "blah" }
629
- expect(b).to be_nil
630
-
631
- expect(ExceptionHandling.last_exception_timestamp).to eq(517_033_020)
632
-
633
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
634
-
635
- expect(sent_notifications.last.enhanced_data['timestamp']).to eq(517_033_020)
636
- end
637
- end
638
-
639
- it "log the error if the exception message is nil" do
640
- capture_notifications
641
-
642
- ExceptionHandling.log_error(exception_with_nil_message)
643
-
644
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
645
- expect(sent_notifications.last.enhanced_data['error_string']).to eq('RuntimeError: ')
646
- end
647
-
648
- it "log the error if the exception message is nil and the exception context is a hash" do
649
- capture_notifications
650
-
651
- ExceptionHandling.log_error(exception_with_nil_message, "SERVER_NAME" => "exceptional.com")
652
-
653
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
654
- expect(sent_notifications.last.enhanced_data['error_string']).to eq('RuntimeError: ')
655
- end
656
-
657
- context "Honeybadger integration" do
658
- context "with Honeybadger not defined" do
659
- before do
660
- allow(ExceptionHandling).to receive(:honeybadger_defined?) { false }
661
- end
662
-
663
- it "not invoke send_exception_to_honeybadger when log_error is executed" do
664
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
665
- ExceptionHandling.log_error(exception_1)
666
- end
667
-
668
- it "not invoke send_exception_to_honeybadger when ensure_safe is executed" do
669
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
670
- ExceptionHandling.ensure_safe { raise exception_1 }
671
- end
672
- end
673
-
674
- context "with Honeybadger defined" do
675
- it "not send_exception_to_honeybadger when log_warning is executed" do
676
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
677
- ExceptionHandling.log_warning("This should not go to honeybadger")
678
- end
679
-
680
- it "not send_exception_to_honeybadger when log_error is called with a Warning" do
681
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
682
- ExceptionHandling.log_error(ExceptionHandling::Warning.new("This should not go to honeybadger"))
683
- end
684
-
685
- it "invoke send_exception_to_honeybadger when log_error is executed" do
686
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
687
- ExceptionHandling.log_error(exception_1)
688
- end
689
-
690
- it "invoke send_exception_to_honeybadger when log_error_rack is executed" do
691
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
692
- ExceptionHandling.log_error_rack(exception_1, {}, nil)
693
- end
694
-
695
- it "invoke send_exception_to_honeybadger when ensure_safe is executed" do
696
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
697
- ExceptionHandling.ensure_safe { raise exception_1 }
698
- end
699
-
700
- it "specify error message as an empty string when notifying honeybadger if exception message is nil" do
701
- expect(Honeybadger).to receive(:notify).with(any_args) do |args|
702
- expect(args[:error_message]).to eq("")
703
- end
704
- ExceptionHandling.log_error(exception_with_nil_message)
705
- end
706
-
707
- context "with stubbed values" do
708
- before do
709
- Time.now_override = Time.now
710
- @env = { server: "fe98" }
711
- @parameters = { advertiser_id: 435, controller: "some_controller" }
712
- @session = { username: "jsmith" }
713
- @request_uri = "host/path"
714
- @controller = create_dummy_controller(@env, @parameters, @session, @request_uri)
715
- allow(ExceptionHandling).to receive(:server_name) { "invoca_fe98" }
716
-
717
- @exception = StandardError.new("Some Exception")
718
- @exception.set_backtrace([
719
- "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
720
- "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
721
- ])
722
- @exception_context = { "SERVER_NAME" => "exceptional.com" }
723
- end
724
-
725
- it "send error details and relevant context data to Honeybadger with log_context" do
726
- honeybadger_data = nil
727
- expect(Honeybadger).to receive(:notify).with(any_args) do |data|
728
- honeybadger_data = data
729
- end
730
- ExceptionHandling.logger.global_context = { service_name: "rails", region: "AWS-us-east-1" }
731
- log_context = { log_source: "gem/listen", service_name: "bin/console" }
732
- ExceptionHandling.log_error(@exception, @exception_context, @controller, **log_context) do |data|
733
- data[:scm_revision] = "5b24eac37aaa91f5784901e9aabcead36fd9df82"
734
- data[:user_details] = { username: "jsmith" }
735
- data[:event_response] = "Event successfully received"
736
- data[:other_section] = "This should not be included in the response"
737
- end
738
-
739
- expected_data = {
740
- error_class: :"Test Exception",
741
- error_message: "Some Exception",
742
- controller: "some_controller",
743
- exception: @exception,
744
- context: {
745
- timestamp: Time.now.to_i,
746
- error_class: "StandardError",
747
- server: "invoca_fe98",
748
- exception_context: { "SERVER_NAME" => "exceptional.com" },
749
- scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
750
- notes: "this is used by a test",
751
- user_details: { "username" => "jsmith" },
752
- request: {
753
- "params" => { "advertiser_id" => 435, "controller" => "some_controller" },
754
- "rails_root" => "Rails.root not defined. Is this a test environment?",
755
- "url" => "host/path"
756
- },
757
- session: {
758
- "key" => nil,
759
- "data" => { "username" => "jsmith" }
760
- },
761
- environment: {
762
- "SERVER_NAME" => "exceptional.com"
763
- },
764
- backtrace: [
765
- "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
766
- "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
767
- ],
768
- event_response: "Event successfully received",
769
- log_context: { "service_name" => "bin/console", "region" => "AWS-us-east-1", "log_source" => "gem/listen" }
770
- }
771
- }
772
- expect(honeybadger_data).to eq(expected_data)
773
- end
774
-
775
- it "send error details and relevant context data to Honeybadger with empty log_context" do
776
- honeybadger_data = nil
777
- expect(Honeybadger).to receive(:notify).with(any_args) do |data|
778
- honeybadger_data = data
779
- end
780
- ExceptionHandling.logger.global_context = {}
781
- log_context = {}
782
- ExceptionHandling.log_error(@exception, @exception_context, @controller, **log_context) do |data|
783
- data[:scm_revision] = "5b24eac37aaa91f5784901e9aabcead36fd9df82"
784
- data[:user_details] = { username: "jsmith" }
785
- data[:event_response] = "Event successfully received"
786
- data[:other_section] = "This should not be included in the response"
787
- end
788
-
789
- expected_data = {
790
- error_class: :"Test Exception",
791
- error_message: "Some Exception",
792
- controller: "some_controller",
793
- exception: @exception,
794
- context: {
795
- timestamp: Time.now.to_i,
796
- error_class: "StandardError",
797
- server: "invoca_fe98",
798
- exception_context: { "SERVER_NAME" => "exceptional.com" },
799
- scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
800
- notes: "this is used by a test",
801
- user_details: { "username" => "jsmith" },
802
- request: {
803
- "params" => { "advertiser_id" => 435, "controller" => "some_controller" },
804
- "rails_root" => "Rails.root not defined. Is this a test environment?",
805
- "url" => "host/path"
806
- },
807
- session: {
808
- "key" => nil,
809
- "data" => { "username" => "jsmith" }
810
- },
811
- environment: {
812
- "SERVER_NAME" => "exceptional.com"
813
- },
814
- backtrace: [
815
- "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
816
- "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
817
- ],
818
- event_response: "Event successfully received"
819
- }
820
- }
821
- expect(honeybadger_data).to eq(expected_data)
822
- end
823
- end
824
-
825
- context "with post_log_error_hook set" do
826
- after do
827
- ExceptionHandling.post_log_error_hook = nil
828
- end
829
-
830
- 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
831
- @honeybadger_status = nil
832
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
833
- filter_list = {
834
- NoHoneybadger: {
835
- error: "suppress Honeybadger notification",
836
- send_to_honeybadger: false
837
- }
838
- }
839
- allow(File).to receive(:mtime) { incrementing_mtime }
840
- expect(YAML).to receive(:load_file).with(any_args) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }.at_least(1)
841
-
842
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger_unless_filtered).with(any_args).exactly(1).and_call_original
843
- expect(Honeybadger).to_not receive(:notify)
844
- ExceptionHandling.log_error(StandardError.new("suppress Honeybadger notification"))
845
- expect(@honeybadger_status).to eq(:skipped)
846
- end
847
-
848
- it "call log error callback with logged_to_honeybadger set to false if an error occurs while attempting to notify honeybadger" do
849
- @honeybadger_status = nil
850
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
851
- expect(Honeybadger).to receive(:notify).with(any_args) { raise "Honeybadger Notification Failure" }
852
- ExceptionHandling.log_error(exception_1)
853
- expect(@honeybadger_status).to eq(:failure)
854
- end
855
-
856
- it "call log error callback with logged_to_honeybadger set to false on unsuccessful honeybadger notification" do
857
- @honeybadger_status = nil
858
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
859
- expect(Honeybadger).to receive(:notify).with(any_args) { false }
860
- ExceptionHandling.log_error(exception_1)
861
- expect(@honeybadger_status).to eq(:failure)
862
- end
863
-
864
- it "call log error callback with logged_to_honeybadger set to true on successful honeybadger notification" do
865
- @honeybadger_status = nil
866
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
867
- expect(Honeybadger).to receive(:notify).with(any_args) { '06220c5a-b471-41e5-baeb-de247da45a56' }
868
- ExceptionHandling.log_error(exception_1)
869
- expect(@honeybadger_status).to eq(:success)
870
- end
871
- end
872
- end
873
- end
874
-
875
- class EventResponse
876
- def to_s
877
- "message from to_s!"
878
- end
879
- end
880
-
881
- it "allow sections to have data with just a to_s method" do
882
- capture_notifications
883
-
884
- ExceptionHandling.log_error("This is my RingSwitch example.") do |data|
885
- data.merge!(event_response: EventResponse.new)
886
- end
887
-
888
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
889
- expect(sent_notifications.last.enhanced_data['event_response'].to_s).to match(/message from to_s!/)
890
- end
891
- end
892
-
893
- it "return the error ID (timestamp)" do
894
- result = ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
895
- expect(result).to eq(ExceptionHandling.last_exception_timestamp)
896
- end
897
-
898
- it "rescue exceptions that happen in log_error" do
899
- allow(ExceptionHandling).to receive(:make_exception) { raise ArgumentError, "Bad argument" }
900
- expect(ExceptionHandling).to receive(:write_exception_to_log).with(satisfy { |ex| ex.to_s['Bad argument'] },
901
- satisfy { |context| context['ExceptionHandlingError: log_error rescued exception while logging Runtime message'] },
902
- any_args)
903
- ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
904
- end
905
-
906
- it "rescue exceptions that happen when log_error yields" do
907
- expect(ExceptionHandling).to receive(:write_exception_to_log).with(satisfy { |ex| ex.to_s['Bad argument'] },
908
- satisfy { |context| context['Context message'] },
909
- anything,
910
- any_args)
911
- ExceptionHandling.log_error(ArgumentError.new("Bad argument"), "Context message") { |_data| raise 'Error!!!' }
912
- end
913
-
914
- context "Exception Filtering" do
915
- before do
916
- filter_list = { exception1: { 'error' => "my error message" },
917
- exception2: { 'error' => "some other message", :session => "misc data" } }
918
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
919
-
920
- # bump modified time up to get the above filter loaded
921
- allow(File).to receive(:mtime) { incrementing_mtime }
922
- end
923
-
924
- it "handle case where filter list is not found" do
925
- allow(YAML).to receive(:load_file) { raise Errno::ENOENT, "File not found" }
926
-
927
- capture_notifications
928
-
929
- ExceptionHandling.log_error("My error message is in list")
930
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
931
- end
932
-
933
- it "log exception and suppress email when exception is on filter list" do
934
- capture_notifications
935
-
936
- ExceptionHandling.log_error("Error message is not in list")
937
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
938
-
939
- sent_notifications.clear
940
- ExceptionHandling.log_error("My error message is in list")
941
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
942
- end
943
-
944
- it "allow filtering exception on any text in exception data" do
945
- filters = { exception1: { session: "data: my extra session data" } }
946
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filters) }
947
-
948
- capture_notifications
949
-
950
- ExceptionHandling.log_error("No match here") do |data|
951
- data[:session] = {
952
- key: "@session_id",
953
- data: "my extra session data"
954
- }
955
- end
956
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
957
-
958
- ExceptionHandling.log_error("No match here") do |data|
959
- data[:session] = {
960
- key: "@session_id",
961
- data: "my extra session <no match!> data"
962
- }
963
- end
964
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
965
- end
966
-
967
- it "reload filter list on the next exception if file was modified" do
968
- capture_notifications
969
-
970
- ExceptionHandling.log_error("Error message is not in list")
971
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
972
-
973
- filter_list = { exception1: { 'error' => "Error message is not in list" } }
974
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
975
- allow(File).to receive(:mtime) { incrementing_mtime }
976
-
977
- sent_notifications.clear
978
- ExceptionHandling.log_error("Error message is not in list")
979
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
980
- end
981
-
982
- it "not consider filter if both error message and body do not match" do
983
- capture_notifications
984
-
985
- # error message matches, but not full text
986
- ExceptionHandling.log_error("some other message")
987
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
988
-
989
- # now both match
990
- sent_notifications.clear
991
- ExceptionHandling.log_error("some other message") do |data|
992
- data[:session] = { some_random_key: "misc data" }
993
- end
994
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
995
- end
996
-
997
- it "skip environment keys not on whitelist" do
998
- capture_notifications
999
-
1000
- ExceptionHandling.log_error("some message") do |data|
1001
- data[:environment] = { SERVER_PROTOCOL: "HTTP/1.0", RAILS_SECRETS_YML_CONTENTS: 'password: VERY_SECRET_PASSWORD' }
1002
- end
1003
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
1004
-
1005
- mail = sent_notifications.last
1006
- environment = mail.enhanced_data['environment']
1007
-
1008
- expect(environment["RAILS_SECRETS_YML_CONTENTS"]).to be_nil, environment.inspect # this is not on whitelist).to be_nil
1009
- expect(environment["SERVER_PROTOCOL"]).to be_truthy, environment.inspect # this is
1010
- end
1011
-
1012
- it "omit environment defaults" do
1013
- capture_notifications
1014
-
1015
- allow(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args) { |exception_info| sent_notifications << exception_info }
1016
-
1017
- ExceptionHandling.log_error("some message") do |data|
1018
- data[:environment] = { SERVER_PORT: '80', SERVER_PROTOCOL: "HTTP/1.0" }
1019
- end
1020
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
1021
- mail = sent_notifications.last
1022
- environment = mail.enhanced_data['environment']
1023
-
1024
- expect(environment["SERVER_PORT"]).to be_nil, environment.inspect # this was default).to be_nil
1025
- expect(environment["SERVER_PROTOCOL"]).to be_truthy, environment.inspect # this was not
1026
- end
1027
-
1028
- it "reject the filter file if any contain all empty regexes" do
1029
- filter_list = { exception1: { 'error' => "", :session => "" },
1030
- exception2: { 'error' => "is not in list", :session => "" } }
1031
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
1032
- allow(File).to receive(:mtime) { incrementing_mtime }
1033
-
1034
- capture_notifications
1035
-
1036
- ExceptionHandling.log_error("Error message is not in list")
1037
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
1038
- end
1039
-
1040
- it "reload filter file if filename changes" do
1041
- catalog = ExceptionHandling.exception_catalog
1042
- ExceptionHandling.filter_list_filename = "./config/other_exception_filters.yml"
1043
- expect(ExceptionHandling.exception_catalog).to_not eq(catalog)
1044
- end
1045
-
1046
- context "Exception Handling Mailer" do
1047
- EXPECTED_SMTP_HASH =
1048
- {
1049
- host: '127.0.0.1',
1050
- domain: 'localhost.localdomain',
1051
- from: 'server@example.com',
1052
- to: 'escalation@example.com'
1053
- }.freeze
1054
-
1055
- [[true, false], [true, true]].each do |em_flag, synchrony_flag|
1056
- context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
1057
- before do
1058
- ExceptionHandling.eventmachine_safe = em_flag
1059
- ExceptionHandling.eventmachine_synchrony = synchrony_flag
1060
- EventMachineStub.block = nil
1061
- set_test_const('EventMachine', EventMachineStub)
1062
- set_test_const('EventMachine::Protocols', Module.new)
1063
- set_test_const('EventMachine::DNS', Module.new)
1064
- set_test_const('EventMachine::DNS::Resolver', DNSResolvStub)
1065
- end
1066
-
1067
- after do
1068
- ExceptionHandling.eventmachine_safe = false
1069
- ExceptionHandling.eventmachine_synchrony = false
1070
- end
1071
-
1072
- it "schedule EventMachine STMP when EventMachine defined" do
1073
- ActionMailer::Base.deliveries.clear
1074
-
1075
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
1076
-
1077
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1078
- expect(EventMachineStub.block).to be_truthy
1079
- EventMachineStub.block.call
1080
- expect(DNSResolvStub.callback_block).to be_truthy
1081
- DNSResolvStub.callback_block.call ['127.0.0.1']
1082
- expect((SmtpClientStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }) .to eq(EXPECTED_SMTP_HASH), SmtpClientStub.send_hash.inspect
1083
- expect(SmtpClientStub.last_method).to eq((synchrony_flag ? :asend : :send))
1084
- expect(SmtpClientStub.send_hash[:content]).to match(/Exception to escalate/)
1085
- assert_emails 0, ActionMailer::Base.deliveries.*.to_s
1086
- end
1087
-
1088
- it "pass the content as a proper rfc 2822 message" do
1089
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
1090
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1091
- expect(EventMachineStub.block).to be_truthy
1092
- EventMachineStub.block.call
1093
- expect(DNSResolvStub.callback_block).to be_truthy
1094
- DNSResolvStub.callback_block.call ['127.0.0.1']
1095
- expect(content = SmtpClientStub.send_hash[:content]).to be_truthy
1096
- expect(content).to match(/Content-Transfer-Encoding: 7bit/)
1097
- expect(content).to match(/\r\n\.\r\n\z/)
1098
- end
1099
-
1100
- it "log fatal on EventMachine STMP errback" do
1101
- ActionMailer::Base.deliveries.clear
1102
-
1103
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
1104
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Exception to escalate/, any_args)
1105
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to email by SMTP: "credential mismatch"/)
1106
-
1107
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1108
- expect(EventMachineStub.block).to be_truthy
1109
- EventMachineStub.block.call
1110
- expect(DNSResolvStub.callback_block).to be_truthy
1111
- DNSResolvStub.callback_block.call(['127.0.0.1'])
1112
- SmtpClientErrbackStub.block.call("credential mismatch")
1113
- 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
1114
-
1115
- it "log fatal on EventMachine dns resolver errback" do
1116
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Exception to escalate/, any_args)
1117
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to resolv DNS for localhost: "softlayer sucks"/)
1118
-
1119
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1120
- expect(EventMachineStub.block).to be_truthy
1121
- EventMachineStub.block.call
1122
- DNSResolvStub.errback_block.call("softlayer sucks")
1123
- end
1124
- end
1125
- end
1126
- end
1127
- end
1128
-
1129
- context "Exception mapping" do
1130
- before do
1131
- @data = {
1132
- environment: {
1133
- 'HTTP_HOST' => "localhost",
1134
- 'HTTP_REFERER' => "http://localhost/action/controller/instance",
1135
- },
1136
- session: {
1137
- data: {
1138
- affiliate_id: defined?(Affiliate) ? Affiliate.first.id : '1',
1139
- edit_mode: true,
1140
- advertiser_id: defined?(Advertiser) ? Advertiser.first.id : '1',
1141
- username_id: defined?(Username) ? Username.first.id : '1',
1142
- user_id: defined?(User) ? User.first.id : '1',
1143
- flash: {},
1144
- impersonated_organization_pk: 'Advertiser_1'
1145
- }
1146
- },
1147
- request: {},
1148
- 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'"],
1149
- api_key: "none",
1150
- error_class: "StandardError",
1151
- error: 'Some error message'
1152
- }
1153
- end
1154
-
1155
- it "clean backtraces" do
1156
- begin
1157
- raise "test exception"
1158
- rescue => ex
1159
- backtrace = ex.backtrace
1160
- end
1161
- result = ExceptionHandling.send(:clean_backtrace, ex).to_s
1162
- expect(backtrace).to_not eq(result)
1163
- end
1164
-
1165
- it "return entire backtrace if cleaned is emtpy" do
1166
- begin
1167
- backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
1168
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
1169
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `__send__'",
1170
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `find'",
1171
- "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `call'",
1172
- "/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. '",
1173
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `__send__'",
1174
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `run'",
1175
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:447:in `_run__1913317170__setup__4__callbacks'",
1176
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `send'",
1177
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `__run_callback'",
1178
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:385:in `_run_setup_callbacks'",
1179
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `send'",
1180
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'",
1181
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:70:in `run'",
1182
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'",
1183
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'",
1184
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'",
1185
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `old_run_suite'",
1186
- "(eval):12:in `run_suite'",
1187
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `send'",
1188
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `start_mediator'",
1189
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:81:in `start'",
1190
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'",
1191
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'",
1192
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:279",
1193
- "-e:1"]
1194
-
1195
- module ::Rails
1196
- class BacktraceCleaner
1197
- def clean(_backtrace)
1198
- []
1199
- end
1200
- end
1201
- end
1202
-
1203
- rails = double(Rails)
1204
- expect(rails).to receive(:backtrace_cleaner) { Rails::BacktraceCleaner.new }
1205
- rails.backtrace_cleaner
1206
-
1207
- ex = Exception.new
1208
- ex.set_backtrace(backtrace)
1209
- result = ExceptionHandling.send(:clean_backtrace, ex)
1210
- expect(result).to eq(backtrace)
1211
- ensure
1212
- Object.send(:remove_const, :Rails)
1213
- end
1214
- end
1215
- end
1216
-
1217
- context "log_perodically" do
1218
- before do
1219
- Time.now_override = Time.now # Freeze time
1220
- ExceptionHandling.logger.clear
1221
- end
1222
-
1223
- after do
1224
- Time.now_override = nil
1225
- end
1226
-
1227
- it "take in additional logging context and pass them to the logger" do
1228
- ExceptionHandling.log_periodically(:test_context_with_periodic, 30.minutes, "this will be written", service_name: 'exception_handling')
1229
- expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
1230
- expect(logged_excluding_reload_filter.last[:context]).to eq({ service_name: 'exception_handling' })
1231
- end
1232
-
1233
- it "log immediately when we are expected to log" do
1234
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
1235
- expect(logged_excluding_reload_filter.size).to eq(1)
1236
-
1237
- Time.now_override = Time.now + 5.minutes
1238
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
1239
- expect(logged_excluding_reload_filter.size).to eq(1)
1240
-
1241
- ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
1242
- expect(logged_excluding_reload_filter.size).to eq(2)
1243
-
1244
- Time.now_override = Time.now + 26.minutes
1245
-
1246
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
1247
- expect(logged_excluding_reload_filter.size).to eq(3)
1248
- end
1249
- end
1250
- end
1251
-
1252
- context "ExceptionHandling < 3.0 " do
1253
- it "should return a deprecation warning" do
1254
- ExceptionHandling.production_support_recipients = "prodsupport@example.com"
1255
- expect { ExceptionHandling.escalate_to_production_support("blah", "invoca@example.com") }
1256
- .to output(/DEPRECATION WARNING: escalate_to_production_support is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1257
- end
1258
-
1259
- it "should return a deprecation warning" do
1260
- expect { ExceptionHandling.escalate_error("blah", "invoca@example.com") }
1261
- .to output(/DEPRECATION WARNING: escalate_error is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1262
- end
1263
-
1264
- it "should return a deprecation warning" do
1265
- expect { ExceptionHandling.escalate_warning("blah", "invoca@example.com") }
1266
- .to output(/DEPRECATION WARNING: escalate_warning is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1267
- end
1268
-
1269
- it "should return a deprecation warning" do
1270
- expect { ExceptionHandling.ensure_escalation("blah", "invoca@example.com") }
1271
- .to output(/DEPRECATION WARNING: ensure_escalation is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1272
- end
1273
- end
1274
-
1275
- private
1276
-
1277
- def logged_excluding_reload_filter
1278
- ExceptionHandling.logger.logged.select { |l| l[:message] !~ /Reloading filter list/ }
1279
- end
1280
-
1281
- def incrementing_mtime
1282
- @mtime ||= Time.now
1283
- @mtime += 1.day
1284
- end
1285
-
1286
- def exception_1
1287
- @exception_1 ||=
1288
- begin
1289
- raise StandardError, "Exception 1"
1290
- rescue => ex
1291
- ex
1292
- end
1293
- end
1294
-
1295
- def exception_2
1296
- @exception_2 ||=
1297
- begin
1298
- raise StandardError, "Exception 2"
1299
- rescue => ex
1300
- ex
1301
- end
1302
- end
1303
- end