bugsnag 6.10.0 → 6.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +4 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +1 -0
- data/README.md +1 -0
- data/VERSION +1 -1
- data/features/fixtures/docker-compose.yml +13 -0
- data/features/fixtures/rails3/app/app/controllers/breadcrumbs_controller.rb +19 -0
- data/features/fixtures/rails3/app/app/controllers/session_tracking_controller.rb +10 -6
- data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +8 -2
- data/features/fixtures/rails3/app/config/routes.rb +1 -0
- data/features/fixtures/rails4/app/Gemfile +5 -1
- data/features/fixtures/rails4/app/app/controllers/breadcrumbs_controller.rb +26 -0
- data/features/fixtures/rails4/app/app/controllers/mongo_controller.rb +23 -0
- data/features/fixtures/rails4/app/app/controllers/session_tracking_controller.rb +9 -5
- data/features/fixtures/rails4/app/app/jobs/application_job.rb +2 -0
- data/features/fixtures/rails4/app/app/jobs/notify_job.rb +5 -0
- data/features/fixtures/rails4/app/app/models/mongo_model.rb +6 -0
- data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +7 -1
- data/features/fixtures/rails4/app/config/mongoid.yml +22 -0
- data/features/fixtures/rails4/app/config/routes.rb +2 -0
- data/features/fixtures/rails5/app/Gemfile +4 -0
- data/features/fixtures/rails5/app/app/controllers/breadcrumbs_controller.rb +24 -0
- data/features/fixtures/rails5/app/app/controllers/mongo_controller.rb +22 -0
- data/features/fixtures/rails5/app/app/controllers/session_tracking_controller.rb +9 -5
- data/features/fixtures/rails5/app/app/jobs/notify_job.rb +5 -0
- data/features/fixtures/rails5/app/app/models/mongo_model.rb +6 -0
- data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +7 -1
- data/features/fixtures/rails5/app/config/mongoid.yml +23 -0
- data/features/fixtures/rails5/app/config/routes.rb +11 -1
- data/features/rails_features/auto_capture_sessions.feature +55 -5
- data/features/rails_features/breadcrumbs.feature +135 -0
- data/features/rails_features/mongo_breadcrumbs.feature +100 -0
- data/features/steps/ruby_notifier_steps.rb +6 -0
- data/lib/bugsnag.rb +59 -3
- data/lib/bugsnag/breadcrumbs/breadcrumb.rb +76 -0
- data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +14 -0
- data/lib/bugsnag/breadcrumbs/validator.rb +59 -0
- data/lib/bugsnag/configuration.rb +103 -6
- data/lib/bugsnag/integrations/mongo.rb +132 -0
- data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +118 -0
- data/lib/bugsnag/integrations/railtie.rb +28 -1
- data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
- data/lib/bugsnag/report.rb +30 -1
- data/lib/bugsnag/session_tracker.rb +1 -0
- data/lib/bugsnag/utility/circular_buffer.rb +62 -0
- data/spec/breadcrumbs/breadcrumb_spec.rb +93 -0
- data/spec/breadcrumbs/validator_spec.rb +200 -0
- data/spec/bugsnag_spec.rb +230 -0
- data/spec/configuration_spec.rb +176 -2
- data/spec/integrations/mongo_spec.rb +262 -0
- data/spec/report_spec.rb +149 -0
- data/spec/session_tracker_spec.rb +24 -2
- data/spec/utility/circular_buffer_spec.rb +98 -0
- metadata +27 -2
data/spec/bugsnag_spec.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe Bugsnag do
|
5
|
+
|
6
|
+
let(:breadcrumbs) { Bugsnag.configuration.breadcrumbs }
|
7
|
+
let(:timestamp_regex) { /^\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:[\d\.]+Z$/ }
|
8
|
+
|
5
9
|
describe 'notify' do
|
6
10
|
before do
|
7
11
|
Bugsnag.configuration.logger = spy('logger')
|
@@ -11,10 +15,91 @@ describe Bugsnag do
|
|
11
15
|
notify_test_exception(true)
|
12
16
|
expect(Bugsnag.configuration.logger).to_not have_received(:warn)
|
13
17
|
end
|
18
|
+
|
14
19
|
it 'logs an error when sending invalid arguments as auto_notify' do
|
15
20
|
notify_test_exception({severity: 'info'})
|
16
21
|
expect(Bugsnag.configuration.logger).to have_received(:warn)
|
17
22
|
end
|
23
|
+
|
24
|
+
it 'leaves a breadcrumb after exception delivery' do
|
25
|
+
begin
|
26
|
+
1/0
|
27
|
+
rescue ZeroDivisionError => e
|
28
|
+
Bugsnag.notify(e)
|
29
|
+
sent_time = Time.now.utc
|
30
|
+
end
|
31
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
32
|
+
breadcrumb = breadcrumbs.to_a.first
|
33
|
+
expect(breadcrumb.name).to eq('ZeroDivisionError')
|
34
|
+
expect(breadcrumb.type).to eq(Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
|
35
|
+
expect(breadcrumb.auto).to eq(true)
|
36
|
+
expect(breadcrumb.meta_data).to eq({
|
37
|
+
:error_class => 'ZeroDivisionError',
|
38
|
+
:message => 'divided by 0',
|
39
|
+
:severity => 'warning'
|
40
|
+
})
|
41
|
+
expect(breadcrumb.timestamp).to be_within(1).of(sent_time)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'leave a RuntimeError breadcrumb after string delivery' do
|
45
|
+
Bugsnag.notify('notified string')
|
46
|
+
sent_time = Time.now.utc
|
47
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
48
|
+
breadcrumb = breadcrumbs.to_a.first
|
49
|
+
expect(breadcrumb.name).to eq('RuntimeError')
|
50
|
+
expect(breadcrumb.type).to eq(Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
|
51
|
+
expect(breadcrumb.auto).to eq(true)
|
52
|
+
expect(breadcrumb.meta_data).to eq({
|
53
|
+
:error_class => 'RuntimeError',
|
54
|
+
:message => 'notified string',
|
55
|
+
:severity => 'warning'
|
56
|
+
})
|
57
|
+
expect(breadcrumb.timestamp).to be_within(1).of(sent_time)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#configure' do
|
62
|
+
it 'calls #check_endpoint_setup every time' do
|
63
|
+
expect(Bugsnag).to receive(:check_endpoint_setup).twice
|
64
|
+
|
65
|
+
Bugsnag.configure
|
66
|
+
Bugsnag.configure
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#check_endpoint_setup' do
|
71
|
+
let(:custom_notify_endpoint) { "Custom notify endpoint" }
|
72
|
+
let(:custom_session_endpoint) { "Custom session endpoint" }
|
73
|
+
|
74
|
+
it "does nothing for default endpoints or if both endpoints are set" do
|
75
|
+
expect(Bugsnag.configuration).not_to receive(:warn)
|
76
|
+
Bugsnag.send(:check_endpoint_setup)
|
77
|
+
|
78
|
+
Bugsnag.configuration.set_endpoints(custom_notify_endpoint, custom_session_endpoint)
|
79
|
+
Bugsnag.send(:check_endpoint_setup)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "warns and disables sessions if a notify endpoint is set without a session endpoint" do
|
83
|
+
expect(Bugsnag.configuration).to receive(:warn).with("The session endpoint has not been set, all further session capturing will be disabled")
|
84
|
+
expect(Bugsnag.configuration).to receive(:disable_sessions)
|
85
|
+
Bugsnag.configuration.set_endpoints(custom_notify_endpoint, nil)
|
86
|
+
Bugsnag.send(:check_endpoint_setup)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "raises an ArgumentError if a session endpoint is set without a notify endpoint" do
|
90
|
+
Bugsnag.configuration.set_endpoints(nil, "custom session endpoint")
|
91
|
+
expect{ Bugsnag.send(:check_endpoint_setup) }.to raise_error(ArgumentError, "The session endpoint cannot be modified without the notify endpoint")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "is called after the configuration block has returned" do
|
95
|
+
expect(Bugsnag.configuration).to receive(:warn).with("The 'endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead").once
|
96
|
+
expect(Bugsnag.configuration).to receive(:warn).with("The 'session_endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead").once
|
97
|
+
expect(Bugsnag.configuration).not_to receive(:warn).with("The session endpoint has not been set, all further session capturing will be disabled")
|
98
|
+
Bugsnag.configure do |configuration|
|
99
|
+
configuration.endpoint = custom_notify_endpoint
|
100
|
+
configuration.session_endpoint = custom_session_endpoint
|
101
|
+
end
|
102
|
+
end
|
18
103
|
end
|
19
104
|
|
20
105
|
describe "add_exit_handler" do
|
@@ -133,4 +218,149 @@ describe Bugsnag do
|
|
133
218
|
Kernel.send(:remove_const, :REQUIRED)
|
134
219
|
end
|
135
220
|
end
|
221
|
+
|
222
|
+
describe ".leave_breadcrumb" do
|
223
|
+
it "requires only a name argument" do
|
224
|
+
Bugsnag.leave_breadcrumb("TestName")
|
225
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
226
|
+
expect(breadcrumbs.first.to_h).to match({
|
227
|
+
:name => "TestName",
|
228
|
+
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
|
229
|
+
:metaData => {},
|
230
|
+
:timestamp => match(timestamp_regex)
|
231
|
+
})
|
232
|
+
end
|
233
|
+
|
234
|
+
it "accepts meta_data" do
|
235
|
+
Bugsnag.leave_breadcrumb("TestName", { :a => 1, :b => "2" })
|
236
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
237
|
+
expect(breadcrumbs.first.to_h).to match({
|
238
|
+
:name => "TestName",
|
239
|
+
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
|
240
|
+
:metaData => { :a => 1, :b => "2" },
|
241
|
+
:timestamp => match(timestamp_regex)
|
242
|
+
})
|
243
|
+
end
|
244
|
+
|
245
|
+
it "allows different message types" do
|
246
|
+
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
|
247
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
248
|
+
expect(breadcrumbs.first.to_h).to match({
|
249
|
+
:name => "TestName",
|
250
|
+
:type => Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE,
|
251
|
+
:metaData => {},
|
252
|
+
:timestamp => match(timestamp_regex)
|
253
|
+
})
|
254
|
+
end
|
255
|
+
|
256
|
+
it "validates before leaving" do
|
257
|
+
Bugsnag.leave_breadcrumb(
|
258
|
+
"123123123123123123123123123123456456456456456456456456456456",
|
259
|
+
{
|
260
|
+
:a => 1,
|
261
|
+
:b => [1, 2, 3, 4],
|
262
|
+
:c => {
|
263
|
+
:test => true,
|
264
|
+
:test2 => false
|
265
|
+
}
|
266
|
+
},
|
267
|
+
"Not a real type"
|
268
|
+
)
|
269
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
270
|
+
expect(breadcrumbs.first.to_h).to match({
|
271
|
+
:name => "123123123123123123123123123123",
|
272
|
+
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
|
273
|
+
:metaData => {
|
274
|
+
:a => 1
|
275
|
+
},
|
276
|
+
:timestamp => match(timestamp_regex)
|
277
|
+
})
|
278
|
+
end
|
279
|
+
|
280
|
+
it "runs callbacks before leaving" do
|
281
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
282
|
+
breadcrumb.meta_data = {
|
283
|
+
:callback => true
|
284
|
+
}
|
285
|
+
end
|
286
|
+
Bugsnag.leave_breadcrumb("TestName")
|
287
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
288
|
+
expect(breadcrumbs.first.to_h).to match({
|
289
|
+
:name => "TestName",
|
290
|
+
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
|
291
|
+
:metaData => {
|
292
|
+
:callback => true
|
293
|
+
},
|
294
|
+
:timestamp => match(timestamp_regex)
|
295
|
+
})
|
296
|
+
end
|
297
|
+
|
298
|
+
it "validates after callbacks" do
|
299
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
300
|
+
breadcrumb.meta_data = {
|
301
|
+
:int => 1,
|
302
|
+
:array => [1, 2, 3],
|
303
|
+
:hash => {
|
304
|
+
:a => 1,
|
305
|
+
:b => 2
|
306
|
+
}
|
307
|
+
}
|
308
|
+
breadcrumb.type = "Not a real type"
|
309
|
+
breadcrumb.name = "123123123123123123123123123123456456456456456"
|
310
|
+
end
|
311
|
+
Bugsnag.leave_breadcrumb("TestName")
|
312
|
+
expect(breadcrumbs.to_a.size).to eq(1)
|
313
|
+
expect(breadcrumbs.first.to_h).to match({
|
314
|
+
:name => "123123123123123123123123123123",
|
315
|
+
:type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
|
316
|
+
:metaData => {
|
317
|
+
:int => 1
|
318
|
+
},
|
319
|
+
:timestamp => match(timestamp_regex)
|
320
|
+
})
|
321
|
+
end
|
322
|
+
|
323
|
+
it "doesn't add when ignored by the validator" do
|
324
|
+
Bugsnag.configuration.enabled_automatic_breadcrumb_types = []
|
325
|
+
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE, :auto)
|
326
|
+
expect(breadcrumbs.to_a.size).to eq(0)
|
327
|
+
end
|
328
|
+
|
329
|
+
it "doesn't add if ignored in a callback" do
|
330
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
331
|
+
breadcrumb.ignore!
|
332
|
+
end
|
333
|
+
Bugsnag.leave_breadcrumb("TestName")
|
334
|
+
expect(breadcrumbs.to_a.size).to eq(0)
|
335
|
+
end
|
336
|
+
|
337
|
+
it "doesn't add when ignored after the callbacks" do
|
338
|
+
Bugsnag.configuration.enabled_automatic_breadcrumb_types = [
|
339
|
+
Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE
|
340
|
+
]
|
341
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
342
|
+
breadcrumb.type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE
|
343
|
+
end
|
344
|
+
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, :auto)
|
345
|
+
expect(breadcrumbs.to_a.size).to eq(0)
|
346
|
+
end
|
347
|
+
|
348
|
+
it "doesn't call callbacks if ignored early" do
|
349
|
+
Bugsnag.configuration.enabled_automatic_breadcrumb_types = []
|
350
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
351
|
+
fail "This shouldn't be called"
|
352
|
+
end
|
353
|
+
Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE, :auto)
|
354
|
+
end
|
355
|
+
|
356
|
+
it "doesn't continue to call callbacks if ignored in them" do
|
357
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
358
|
+
breadcrumb.ignore!
|
359
|
+
end
|
360
|
+
Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
|
361
|
+
fail "This shouldn't be called"
|
362
|
+
end
|
363
|
+
Bugsnag.leave_breadcrumb("TestName")
|
364
|
+
end
|
365
|
+
end
|
136
366
|
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -22,10 +22,93 @@ describe Bugsnag::Configuration do
|
|
22
22
|
subject.delivery_method = :wow
|
23
23
|
expect(subject.delivery_method).to eq(:wow)
|
24
24
|
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#notify_endpoint" do
|
28
|
+
it "defaults to DEFAULT_NOTIFY_ENDPOINT" do
|
29
|
+
expect(subject.notify_endpoint).to eq(Bugsnag::Configuration::DEFAULT_NOTIFY_ENDPOINT)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is readonly" do
|
33
|
+
expect{ subject.notify_endpoint = "My Custom Url" }.to raise_error(NoMethodError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is the same as endpoint" do
|
37
|
+
expect(subject.notify_endpoint).to equal(subject.endpoint)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#session_endpoint" do
|
42
|
+
it "defaults to DEFAULT_SESSION_ENDPOINT" do
|
43
|
+
expect(subject.session_endpoint).to eq(Bugsnag::Configuration::DEFAULT_SESSION_ENDPOINT)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#auto_capture_sessions" do
|
48
|
+
it "defaults to true" do
|
49
|
+
expect(subject.auto_capture_sessions).to eq(true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#enable_sessions" do
|
54
|
+
it "defaults to true" do
|
55
|
+
expect(subject.enable_sessions).to eq(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "is readonly" do
|
59
|
+
expect{ subject.enable_sessions = true }.to raise_error(NoMethodError)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#endpoint=" do
|
64
|
+
let(:custom_notify_endpoint) { "My custom notify endpoint" }
|
65
|
+
let(:session_endpoint) { "My session endpoint" }
|
66
|
+
it "calls #warn with a deprecation notice" do
|
67
|
+
allow(subject).to receive(:set_endpoints)
|
68
|
+
expect(subject).to receive(:warn).with("The 'endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead")
|
69
|
+
subject.endpoint = custom_notify_endpoint
|
70
|
+
end
|
71
|
+
|
72
|
+
it "calls #set_endpoints with the new notify_endpoint and existing session endpoint" do
|
73
|
+
allow(subject).to receive(:session_endpoint).and_return(session_endpoint)
|
74
|
+
allow(subject).to receive(:warn)
|
75
|
+
expect(subject).to receive(:set_endpoints).with(custom_notify_endpoint, session_endpoint)
|
76
|
+
subject.endpoint = custom_notify_endpoint
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#session_endpoint=" do
|
81
|
+
let(:notify_endpoint) { "My notify endpoint" }
|
82
|
+
let(:custom_session_endpoint) { "My custom session endpoint" }
|
83
|
+
it "calls #warn with a deprecation notice" do
|
84
|
+
allow(subject).to receive(:set_endpoints)
|
85
|
+
expect(subject).to receive(:warn).with("The 'session_endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead")
|
86
|
+
subject.session_endpoint = custom_session_endpoint
|
87
|
+
end
|
88
|
+
|
89
|
+
it "calls #set_endpoints with the existing notify_endpoint and new session endpoint" do
|
90
|
+
allow(subject).to receive(:notify_endpoint).and_return(notify_endpoint)
|
91
|
+
allow(subject).to receive(:warn)
|
92
|
+
expect(subject).to receive(:set_endpoints).with(notify_endpoint, custom_session_endpoint)
|
93
|
+
subject.session_endpoint = custom_session_endpoint
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#set_endpoints" do
|
98
|
+
let(:custom_notify_endpoint) { "My custom notify endpoint" }
|
99
|
+
let(:custom_session_endpoint) { "My custom session endpoint" }
|
100
|
+
it "set notify_endpoint and session_endpoint" do
|
101
|
+
subject.set_endpoints(custom_notify_endpoint, custom_session_endpoint)
|
102
|
+
expect(subject.notify_endpoint).to eq(custom_notify_endpoint)
|
103
|
+
expect(subject.session_endpoint).to eq(custom_session_endpoint)
|
104
|
+
end
|
105
|
+
end
|
25
106
|
|
26
|
-
|
27
|
-
|
107
|
+
describe "#disable_sessions" do
|
108
|
+
it "sets #send_session and #auto_capture_sessions to false" do
|
109
|
+
subject.disable_sessions
|
28
110
|
expect(subject.auto_capture_sessions).to be false
|
111
|
+
expect(subject.enable_sessions).to be false
|
29
112
|
end
|
30
113
|
end
|
31
114
|
|
@@ -222,4 +305,95 @@ describe Bugsnag::Configuration do
|
|
222
305
|
expect(subject.ignore_classes).to eq(Set.new([SystemExit, SignalException]))
|
223
306
|
end
|
224
307
|
|
308
|
+
describe "#breadcrumbs" do
|
309
|
+
it "first returns a new circular buffer" do
|
310
|
+
buffer = subject.breadcrumbs
|
311
|
+
|
312
|
+
expect(buffer).to be_a(Bugsnag::Utility::CircularBuffer)
|
313
|
+
expect(buffer.to_a).to eq([])
|
314
|
+
end
|
315
|
+
|
316
|
+
it "returns the same buffer in repeated calls" do
|
317
|
+
buffer = subject.breadcrumbs
|
318
|
+
buffer << 1
|
319
|
+
second_buffer = subject.breadcrumbs
|
320
|
+
|
321
|
+
expect(second_buffer.to_a).to eq([1])
|
322
|
+
end
|
323
|
+
|
324
|
+
it "returns a different buffer on different threads" do
|
325
|
+
buffer = subject.breadcrumbs
|
326
|
+
buffer << 1
|
327
|
+
|
328
|
+
second_buffer = nil
|
329
|
+
Thread.new { second_buffer = subject.breadcrumbs; second_buffer << 2 }.join
|
330
|
+
|
331
|
+
expect(buffer.to_a).to eq([1])
|
332
|
+
expect(second_buffer.to_a).to eq([2])
|
333
|
+
end
|
334
|
+
|
335
|
+
it "sets max_items to the current max_breadcrumbs size" do
|
336
|
+
expect(subject.breadcrumbs.max_items).to eq(subject.max_breadcrumbs)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
describe "#max_breadcrumbs" do
|
341
|
+
it "defaults to DEFAULT_MAX_BREADCRUMBS" do
|
342
|
+
expect(subject.max_breadcrumbs).to eq(Bugsnag::Configuration::DEFAULT_MAX_BREADCRUMBS)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe "#max_breadcrumbs=" do
|
347
|
+
it "sets the value of max_breadcrumbs" do
|
348
|
+
subject.max_breadcrumbs = 10
|
349
|
+
expect(subject.max_breadcrumbs).to eq(10)
|
350
|
+
end
|
351
|
+
|
352
|
+
it "sets the max_items property of the breadcrumbs buffer" do
|
353
|
+
buffer = subject.breadcrumbs
|
354
|
+
|
355
|
+
expect(buffer.max_items).to eq(Bugsnag::Configuration::DEFAULT_MAX_BREADCRUMBS)
|
356
|
+
|
357
|
+
subject.max_breadcrumbs = 5
|
358
|
+
|
359
|
+
expect(buffer.max_items).to eq(5)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
describe "#enabled_automatic_breadcrumb_types" do
|
364
|
+
it "defaults to Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES" do
|
365
|
+
expect(subject.enabled_automatic_breadcrumb_types).to eq(Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES)
|
366
|
+
end
|
367
|
+
|
368
|
+
it "is an editable array" do
|
369
|
+
subject.enabled_automatic_breadcrumb_types << "Some custom type"
|
370
|
+
expect(subject.enabled_automatic_breadcrumb_types).to include("Some custom type")
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
describe "#before_breadcrumb_callbacks" do
|
375
|
+
it "initially returns an empty array" do
|
376
|
+
expect(subject.before_breadcrumb_callbacks).to eq([])
|
377
|
+
end
|
378
|
+
|
379
|
+
it "stores the array between subsequent calls" do
|
380
|
+
first_call = subject.before_breadcrumb_callbacks
|
381
|
+
first_call << 1
|
382
|
+
|
383
|
+
second_call = subject.before_breadcrumb_callbacks
|
384
|
+
|
385
|
+
expect(second_call).to eq([1])
|
386
|
+
end
|
387
|
+
|
388
|
+
it "stays the same across threads" do
|
389
|
+
first_array = subject.before_breadcrumb_callbacks
|
390
|
+
first_array << 1
|
391
|
+
|
392
|
+
second_array = nil
|
393
|
+
Thread.new { second_array = subject.before_breadcrumb_callbacks; second_array << 2}.join
|
394
|
+
|
395
|
+
expect(first_array).to eq([1, 2])
|
396
|
+
expect(second_array).to eq([1, 2])
|
397
|
+
end
|
398
|
+
end
|
225
399
|
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Bugsnag::MongoBreadcrumbSubscriber', :order => :defined do
|
5
|
+
before do
|
6
|
+
unless defined?(::Mongo)
|
7
|
+
@mocked_mongo = true
|
8
|
+
module ::Mongo
|
9
|
+
module Monitoring
|
10
|
+
COMMAND = 'Command'
|
11
|
+
module Global
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
module Kernel
|
16
|
+
alias_method :old_require, :require
|
17
|
+
def require(path)
|
18
|
+
old_require(path) unless path == 'mongo'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should subscribe to the mongo monitoring service" do
|
25
|
+
expect(::Mongo::Monitoring::Global).to receive(:subscribe) do |command, subscriber|
|
26
|
+
expect(command).to eq(::Mongo::Monitoring::COMMAND)
|
27
|
+
expect(subscriber).to be_an_instance_of(::Bugsnag::MongoBreadcrumbSubscriber)
|
28
|
+
end
|
29
|
+
load './lib/bugsnag/integrations/mongo.rb'
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with the module loaded" do
|
33
|
+
before do
|
34
|
+
allow(::Mongo::Monitoring::Global).to receive(:subscribe)
|
35
|
+
require './lib/bugsnag/integrations/mongo'
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:subscriber) { Bugsnag::MongoBreadcrumbSubscriber.new }
|
39
|
+
|
40
|
+
describe "#started" do
|
41
|
+
it "calls #leave_command with the event" do
|
42
|
+
event = double
|
43
|
+
expect(subscriber).to receive(:leave_command).with(event)
|
44
|
+
subscriber.started(event)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#succeeded" do
|
49
|
+
it "calls #leave_mongo_breadcrumb with the event_name and event" do
|
50
|
+
event = double
|
51
|
+
expect(subscriber).to receive(:leave_mongo_breadcrumb).with("succeeded", event)
|
52
|
+
subscriber.succeeded(event)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#failed" do
|
57
|
+
it "calls #leave_mongo_breadcrumb with the event_name and event" do
|
58
|
+
event = double
|
59
|
+
expect(subscriber).to receive(:leave_mongo_breadcrumb).with("failed", event)
|
60
|
+
subscriber.failed(event)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#leave_mongo_breadcrumb" do
|
65
|
+
let(:event) { double(
|
66
|
+
:command_name => "command",
|
67
|
+
:database_name => "database",
|
68
|
+
:operation_id => "1234567890",
|
69
|
+
:request_id => "123456",
|
70
|
+
:duration => "123.456"
|
71
|
+
) }
|
72
|
+
let(:event_name) { "event_name" }
|
73
|
+
it "leaves a breadcrumb with relevant meta_data, message, type, and automatic notation" do
|
74
|
+
expect(Bugsnag).to receive(:leave_breadcrumb).with(
|
75
|
+
"Mongo query #{event_name}",
|
76
|
+
{
|
77
|
+
:event_name => "mongo.#{event_name}",
|
78
|
+
:command_name => "command",
|
79
|
+
:database_name => "database",
|
80
|
+
:operation_id => "1234567890",
|
81
|
+
:request_id => "123456",
|
82
|
+
:duration => "123.456"
|
83
|
+
},
|
84
|
+
"process",
|
85
|
+
:auto
|
86
|
+
)
|
87
|
+
subscriber.send(:leave_mongo_breadcrumb, event_name, event)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "adds message data if present" do
|
91
|
+
allow(event).to receive(:message).and_return("This is a message")
|
92
|
+
expect(Bugsnag).to receive(:leave_breadcrumb).with(
|
93
|
+
"Mongo query #{event_name}",
|
94
|
+
{
|
95
|
+
:event_name => "mongo.#{event_name}",
|
96
|
+
:command_name => "command",
|
97
|
+
:database_name => "database",
|
98
|
+
:operation_id => "1234567890",
|
99
|
+
:request_id => "123456",
|
100
|
+
:duration => "123.456",
|
101
|
+
:message => "This is a message"
|
102
|
+
},
|
103
|
+
"process",
|
104
|
+
:auto
|
105
|
+
)
|
106
|
+
subscriber.send(:leave_mongo_breadcrumb, event_name, event)
|
107
|
+
end
|
108
|
+
|
109
|
+
context "command data is present" do
|
110
|
+
let(:command) {
|
111
|
+
{
|
112
|
+
"command" => "collection_name_command",
|
113
|
+
"collection" => "collection_name_getMore",
|
114
|
+
"filter" => nil
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
it "adds the collection name" do
|
119
|
+
expect(subscriber).to receive(:pop_command).with("123456").and_return(command)
|
120
|
+
expect(Bugsnag).to receive(:leave_breadcrumb).with(
|
121
|
+
"Mongo query #{event_name}",
|
122
|
+
{
|
123
|
+
:event_name => "mongo.#{event_name}",
|
124
|
+
:command_name => "command",
|
125
|
+
:database_name => "database",
|
126
|
+
:operation_id => "1234567890",
|
127
|
+
:request_id => "123456",
|
128
|
+
:duration => "123.456",
|
129
|
+
:collection => "collection_name_command"
|
130
|
+
},
|
131
|
+
"process",
|
132
|
+
:auto
|
133
|
+
)
|
134
|
+
subscriber.send(:leave_mongo_breadcrumb, event_name, event)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "adds the correct collection name for 'getMore' commands" do
|
138
|
+
allow(event).to receive(:command_name).and_return("getMore")
|
139
|
+
expect(subscriber).to receive(:pop_command).with("123456").and_return(command)
|
140
|
+
expect(Bugsnag).to receive(:leave_breadcrumb).with(
|
141
|
+
"Mongo query #{event_name}",
|
142
|
+
{
|
143
|
+
:event_name => "mongo.#{event_name}",
|
144
|
+
:command_name => "getMore",
|
145
|
+
:database_name => "database",
|
146
|
+
:operation_id => "1234567890",
|
147
|
+
:request_id => "123456",
|
148
|
+
:duration => "123.456",
|
149
|
+
:collection => "collection_name_getMore"
|
150
|
+
},
|
151
|
+
"process",
|
152
|
+
:auto
|
153
|
+
)
|
154
|
+
subscriber.send(:leave_mongo_breadcrumb, event_name, event)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "adds a JSON string of filter data" do
|
158
|
+
command["filter"] = {"a" => 1, "b" => 2, "$or" => [{"c" => 3}, {"d" => 4}]}
|
159
|
+
expect(subscriber).to receive(:pop_command).with("123456").and_return(command)
|
160
|
+
expect(Bugsnag).to receive(:leave_breadcrumb).with(
|
161
|
+
"Mongo query #{event_name}",
|
162
|
+
{
|
163
|
+
:event_name => "mongo.#{event_name}",
|
164
|
+
:command_name => "command",
|
165
|
+
:database_name => "database",
|
166
|
+
:operation_id => "1234567890",
|
167
|
+
:request_id => "123456",
|
168
|
+
:duration => "123.456",
|
169
|
+
:collection => "collection_name_command",
|
170
|
+
:filter => '{"a":"?","b":"?","$or":[{"c":"?"},{"d":"?"}]}'
|
171
|
+
},
|
172
|
+
"process",
|
173
|
+
:auto
|
174
|
+
)
|
175
|
+
subscriber.send(:leave_mongo_breadcrumb, event_name, event)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "#sanitize_filter_hash" do
|
181
|
+
it "calls into #sanitize_filter_value with the value from each {k,v} pair" do
|
182
|
+
expect(subscriber.send(:sanitize_filter_hash, {:a => 1, :b => 2})).to eq({:a => '?', :b => '?'})
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "#sanitize_filter_value" do
|
187
|
+
it "returns '?' for strings, numbers, booleans, and nil" do
|
188
|
+
expect(subscriber.send(:sanitize_filter_value, 523, 0)).to eq('?')
|
189
|
+
expect(subscriber.send(:sanitize_filter_value, "string", 0)).to eq('?')
|
190
|
+
expect(subscriber.send(:sanitize_filter_value, true, 0)).to eq('?')
|
191
|
+
expect(subscriber.send(:sanitize_filter_value, nil, 0)).to eq('?')
|
192
|
+
end
|
193
|
+
|
194
|
+
it "is recursive and iterative for array values" do
|
195
|
+
expect(subscriber.send(:sanitize_filter_value, [1, [2, [3]]], 0)).to eq(['?', ['?', ['?']]])
|
196
|
+
end
|
197
|
+
|
198
|
+
it "calls #sanitize_filter_hash for hash values" do
|
199
|
+
expect(subscriber).to receive(:sanitize_filter_hash).with({:a => 1}, 1)
|
200
|
+
subscriber.send(:sanitize_filter_value, {:a => 1}, 0)
|
201
|
+
end
|
202
|
+
|
203
|
+
it "returns [MAX_FILTER_DEPTH_REACHED] if the filter depth is exceeded" do
|
204
|
+
expect(subscriber.send(:sanitize_filter_value, 1, 4)).to eq('[MAX_FILTER_DEPTH_REACHED]')
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#leave_command" do
|
209
|
+
it "extracts and stores the command by request_id" do
|
210
|
+
request_id = "123456"
|
211
|
+
command = "this is a command string"
|
212
|
+
event = double(:command => command, :request_id => request_id)
|
213
|
+
|
214
|
+
subscriber.send(:leave_command, event)
|
215
|
+
command_hash = Bugsnag.configuration.request_data[Bugsnag::MongoBreadcrumbSubscriber::MONGO_COMMAND_KEY]
|
216
|
+
expect(command_hash[request_id]).to eq(command)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe "#pop_command" do
|
221
|
+
let(:request_id) { "123456" }
|
222
|
+
let(:command) { "this is a command string" }
|
223
|
+
before do
|
224
|
+
event = double(:command => command, :request_id => request_id)
|
225
|
+
subscriber.send(:leave_command, event)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "returns the command given a request_id" do
|
229
|
+
expect(subscriber.send(:pop_command, request_id)).to eq(command)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "removes the command from the request_data" do
|
233
|
+
subscriber.send(:pop_command, request_id)
|
234
|
+
command_hash = Bugsnag.configuration.request_data[Bugsnag::MongoBreadcrumbSubscriber::MONGO_COMMAND_KEY]
|
235
|
+
expect(command_hash).not_to have_key(request_id)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "returns nil if the request_id is not found" do
|
239
|
+
expect(subscriber.send(:pop_command, "09876")).to be_nil
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe "#event_commands" do
|
244
|
+
it "returns a hash" do
|
245
|
+
expect(subscriber.send(:event_commands)).to be_a(Hash)
|
246
|
+
end
|
247
|
+
|
248
|
+
it "is stored in request data" do
|
249
|
+
subscriber.send(:event_commands)[:key] = "value"
|
250
|
+
command_hash = Bugsnag.configuration.request_data[Bugsnag::MongoBreadcrumbSubscriber::MONGO_COMMAND_KEY]
|
251
|
+
expect(command_hash[:key]).to eq("value")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
after do
|
257
|
+
Object.send(:remove_const, :Mongo) if @mocked_mongo
|
258
|
+
module Kernel
|
259
|
+
alias_method :require, :old_require
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|