exception_handling 2.13.0 → 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 +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