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