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