bugsnag 6.10.0 → 6.11.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 +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
|