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