bugsnag 6.14.0 → 6.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/VERSION +1 -1
  4. data/features/fixtures/docker-compose.yml +5 -1
  5. data/features/fixtures/plain/app/report_modification/initiators/handled_on_error.rb +10 -0
  6. data/features/fixtures/plain/app/report_modification/initiators/unhandled_on_error.rb +11 -0
  7. data/features/fixtures/plain/app/stack_frame_modification/initiators/handled_on_error.rb +29 -0
  8. data/features/fixtures/plain/app/stack_frame_modification/initiators/unhandled_on_error.rb +26 -0
  9. data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +8 -0
  10. data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +8 -0
  11. data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +8 -0
  12. data/features/fixtures/rails6/app/config/initializers/bugsnag.rb +8 -0
  13. data/features/plain_features/add_tab.feature +7 -1
  14. data/features/plain_features/ignore_report.feature +2 -0
  15. data/features/plain_features/report_api_key.feature +3 -1
  16. data/features/plain_features/report_severity.feature +2 -0
  17. data/features/plain_features/report_stack_frames.feature +4 -0
  18. data/features/plain_features/report_user.feature +7 -1
  19. data/features/rails_features/on_error.feature +29 -0
  20. data/lib/bugsnag.rb +35 -0
  21. data/lib/bugsnag/code_extractor.rb +137 -0
  22. data/lib/bugsnag/configuration.rb +27 -0
  23. data/lib/bugsnag/middleware_stack.rb +38 -3
  24. data/lib/bugsnag/on_error_callbacks.rb +33 -0
  25. data/lib/bugsnag/report.rb +1 -1
  26. data/lib/bugsnag/session_tracker.rb +3 -3
  27. data/lib/bugsnag/stacktrace.rb +25 -68
  28. data/spec/code_extractor_spec.rb +129 -0
  29. data/spec/fixtures/crashes/file1.rb +29 -0
  30. data/spec/fixtures/crashes/file2.rb +25 -0
  31. data/spec/fixtures/crashes/file_with_long_lines.rb +7 -0
  32. data/spec/fixtures/crashes/functions.rb +29 -0
  33. data/spec/fixtures/crashes/short_file.rb +2 -0
  34. data/spec/on_error_spec.rb +332 -0
  35. data/spec/report_spec.rb +7 -4
  36. data/spec/spec_helper.rb +8 -0
  37. data/spec/stacktrace_spec.rb +276 -30
  38. metadata +15 -2
@@ -15,10 +15,10 @@ describe Bugsnag::Stacktrace do
15
15
  Bugsnag.notify(e)
16
16
  end
17
17
 
18
- expect(Bugsnag).to have_sent_notification{ |payload, headers|
19
- exception = get_exception_from_payload(payload)
20
- starting_line = __LINE__ - 13
21
- expect(exception["stacktrace"][0]["code"]).to eq({
18
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
19
+ starting_line = __LINE__ - 12
20
+
21
+ expect(get_code_from_payload(payload).to_a).to eq({
22
22
  (starting_line + 0).to_s => ' _a = 1',
23
23
  (starting_line + 1).to_s => ' _b = 2',
24
24
  (starting_line + 2).to_s => ' _c = 3',
@@ -26,7 +26,7 @@ describe Bugsnag::Stacktrace do
26
26
  (starting_line + 4).to_s => ' _d = 4',
27
27
  (starting_line + 5).to_s => ' _e = 5',
28
28
  (starting_line + 6).to_s => ' _f = 6'
29
- })
29
+ }.to_a)
30
30
  }
31
31
  end
32
32
 
@@ -35,19 +35,16 @@ describe Bugsnag::Stacktrace do
35
35
 
36
36
  notify_test_exception
37
37
 
38
- expect(Bugsnag).to have_sent_notification{ |payload, headers|
39
- exception = get_exception_from_payload(payload)
40
- expect(exception["stacktrace"][1]["code"]).to eq(nil)
38
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
39
+ expect(get_code_from_payload(payload)).to eq(nil)
41
40
  }
42
41
  end
43
42
 
44
43
  it 'should send the first 7 lines of the file for exceptions near the top' do
45
44
  load 'spec/fixtures/crashes/start_of_file.rb' rescue Bugsnag.notify $!
46
45
 
47
- expect(Bugsnag).to have_sent_notification{ |payload, headers|
48
- exception = get_exception_from_payload(payload)
49
-
50
- expect(exception["stacktrace"][0]["code"]).to eq({
46
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
47
+ expect(get_code_from_payload(payload).to_a).to eq({
51
48
  "1" => "#",
52
49
  "2" => "raise 'hell'",
53
50
  "3" => "#",
@@ -55,17 +52,15 @@ describe Bugsnag::Stacktrace do
55
52
  "5" => "#",
56
53
  "6" => "#",
57
54
  "7" => "#"
58
- })
55
+ }.to_a)
59
56
  }
60
57
  end
61
58
 
62
59
  it 'should send the last 7 lines of the file for exceptions near the bottom' do
63
60
  load 'spec/fixtures/crashes/end_of_file.rb' rescue Bugsnag.notify $!
64
61
 
65
- expect(Bugsnag).to have_sent_notification{ |payload, headers|
66
- exception = get_exception_from_payload(payload)
67
-
68
- expect(exception["stacktrace"][0]["code"]).to eq({
62
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
63
+ expect(get_code_from_payload(payload)).to eq({
69
64
  "3" => "#",
70
65
  "4" => "#",
71
66
  "5" => "#",
@@ -77,17 +72,226 @@ describe Bugsnag::Stacktrace do
77
72
  }
78
73
  end
79
74
 
80
- it 'should send the last 7 lines of the file for exceptions near the bottom' do
75
+ it 'should send every line of a very short file' do
81
76
  load 'spec/fixtures/crashes/short_file.rb' rescue Bugsnag.notify $!
82
77
 
83
- expect(Bugsnag).to have_sent_notification{ |payload, headers|
84
- exception = get_exception_from_payload(payload)
78
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
79
+ expect(get_code_from_payload(payload).to_a).to eq({
80
+ "1" => "#",
81
+ "2" => "raise 'hell'",
82
+ "3" => "#"
83
+ }.to_a)
84
+ }
85
+ end
85
86
 
86
- expect(exception["stacktrace"][0]["code"]).to eq({
87
- "1" => "raise 'hell'"
88
- })
87
+ it 'should send code for each line in the stacktrace' do
88
+ load 'spec/fixtures/crashes/functions.rb' rescue Bugsnag.notify $!
89
+
90
+ expected_code = [
91
+ # The topmost frame is centered on where the exception was raised
92
+ {
93
+ "11" => "end",
94
+ "12" => "",
95
+ "13" => "def xyz",
96
+ "14" => " raise 'uh oh'",
97
+ "15" => "end",
98
+ "16" => "",
99
+ "17" => "def abc"
100
+ },
101
+ # then we get 'baz' which is where 'xyz' was called
102
+ {
103
+ "7" => "end",
104
+ "8" => "",
105
+ "9" => "def baz",
106
+ "10" => " xyz",
107
+ "11" => "end",
108
+ "12" => "",
109
+ "13" => "def xyz"
110
+ },
111
+ # then we get 'bar' which is where 'baz' was called
112
+ {
113
+ "3" => "end",
114
+ "4" => "",
115
+ "5" => "def bar",
116
+ "6" => " baz",
117
+ "7" => "end",
118
+ "8" => "",
119
+ "9" => "def baz"
120
+ },
121
+ # then we get 'foo' which is where 'bar' was called - this is the first
122
+ # 7 lines because the call to 'bar' is on line 2
123
+ {
124
+ "1" => "def foo",
125
+ "2" => " bar",
126
+ "3" => "end",
127
+ "4" => "",
128
+ "5" => "def bar",
129
+ "6" => " baz",
130
+ "7" => "end"
131
+ },
132
+ # finally we get the call to 'foo' - this is the last 7 lines because
133
+ # the call is on the last line of the file
134
+ {
135
+ "23" => "end",
136
+ "24" => "",
137
+ "25" => "def abcdefghi",
138
+ "26" => " puts 'abcdefghi'",
139
+ "27" => "end",
140
+ "28" => "",
141
+ "29" => "foo"
142
+ }
143
+ ]
144
+
145
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
146
+ (0...expected_code.size).each do |index|
147
+ expect(get_code_from_payload(payload, index).to_a).to eq(expected_code[index].to_a)
148
+ end
149
+ }
150
+ end
151
+
152
+ it 'should send code for each line in the stacktrace when split over multiple files' do
153
+ load 'spec/fixtures/crashes/file1.rb' rescue Bugsnag.notify $!
154
+
155
+ expected_code = [
156
+ {
157
+ "8" => " end",
158
+ "9" => "",
159
+ "10" => " def self.baz2",
160
+ "11" => " raise 'uh oh'",
161
+ "12" => " end",
162
+ "13" => "",
163
+ "14" => " def self.abc2"
164
+ },
165
+ {
166
+ "10" => " end",
167
+ "11" => "",
168
+ "12" => " def self.baz1",
169
+ "13" => " File2.baz2",
170
+ "14" => " end",
171
+ "15" => "",
172
+ "16" => " def self.abc1"
173
+ },
174
+ {
175
+ "4" => " end",
176
+ "5" => "",
177
+ "6" => " def self.bar2",
178
+ "7" => " File1.baz1",
179
+ "8" => " end",
180
+ "9" => "",
181
+ "10" => " def self.baz2"
182
+ },
183
+ {
184
+ "6" => " end",
185
+ "7" => "",
186
+ "8" => " def self.bar1",
187
+ "9" => " File2.bar2",
188
+ "10" => " end",
189
+ "11" => "",
190
+ "12" => " def self.baz1",
191
+ },
192
+ {
193
+ "1" => "module File2",
194
+ "2" => " def self.foo2",
195
+ "3" => " File1.bar1",
196
+ "4" => " end",
197
+ "5" => "",
198
+ "6" => " def self.bar2",
199
+ "7" => " File1.baz1"
200
+ },
201
+ {
202
+ "2" => "",
203
+ "3" => "module File1",
204
+ "4" => " def self.foo1",
205
+ "5" => " File2.foo2",
206
+ "6" => " end",
207
+ "7" => "",
208
+ "8" => " def self.bar1"
209
+ },
210
+ {
211
+ "23" => "",
212
+ "24" => " def self.abcdefghi1",
213
+ "25" => " puts 'abcdefghi1'",
214
+ "26" => " end",
215
+ "27" => "end",
216
+ "28" => "",
217
+ "29" => "File1.foo1"
218
+ }
219
+ ]
220
+
221
+ expect(Bugsnag).to have_sent_notification { |payload, headers|
222
+ (0...expected_code.size).each do |index|
223
+ expect(get_code_from_payload(payload, index).to_a).to eq(expected_code[index].to_a)
224
+ end
89
225
  }
90
226
  end
227
+
228
+ it "can extract code from paths that will be mangled by the project root" do
229
+ # Set the project root to a nested directory, which will then be stripped
230
+ # from the file paths in the API call. This ensures that we read the files
231
+ # based off of the original path, rather than the final file path, e.g.
232
+ # "spec/fixtures/crashes/file1.rb" will be "file1.rb" in the API call, which
233
+ # isn't a path that's possible to read
234
+ project_root = "#{File.dirname(File.dirname(__FILE__))}/spec/fixtures/crashes"
235
+
236
+ configuration = Bugsnag::Configuration.new
237
+ configuration.project_root = project_root
238
+
239
+ backtrace = [
240
+ "spec/fixtures/crashes/file1.rb:13:in `baz1'",
241
+ "./spec/fixtures/crashes/functions.rb:17:in `abc'",
242
+ "#{project_root}/file2.rb:19:in `abcdef2'",
243
+ ]
244
+
245
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
246
+
247
+ expect(stacktrace).to eq([
248
+ {
249
+ file: "file1.rb",
250
+ lineNumber: 13,
251
+ method: "baz1",
252
+ inProject: true,
253
+ code: {
254
+ 10 => " end",
255
+ 11 => "",
256
+ 12 => " def self.baz1",
257
+ 13 => " File2.baz2",
258
+ 14 => " end",
259
+ 15 => "",
260
+ 16 => " def self.abc1"
261
+ }
262
+ },
263
+ {
264
+ file: "functions.rb",
265
+ lineNumber: 17,
266
+ method: "abc",
267
+ inProject: true,
268
+ code: {
269
+ 14 => " raise 'uh oh'",
270
+ 15 => "end",
271
+ 16 => "",
272
+ 17 => "def abc",
273
+ 18 => " puts 'abc'",
274
+ 19 => "end",
275
+ 20 => ""
276
+ },
277
+ },
278
+ {
279
+ file: "file2.rb",
280
+ lineNumber: 19,
281
+ method: "abcdef2",
282
+ inProject: true,
283
+ code: {
284
+ 16 => " end",
285
+ 17 => "",
286
+ 18 => " def self.abcdef2",
287
+ 19 => " puts 'abcdef2'",
288
+ 20 => " end",
289
+ 21 => "",
290
+ 22 => " def self.abcdefghi2"
291
+ },
292
+ },
293
+ ])
294
+ end
91
295
  end
92
296
 
93
297
  context "file paths" do
@@ -102,7 +306,7 @@ describe Bugsnag::Stacktrace do
102
306
  "/foo/bar/.bundle/lib/ignore_me.rb:4:in `to_s'",
103
307
  ]
104
308
 
105
- stacktrace = Bugsnag::Stacktrace.new(backtrace, configuration).to_a
309
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
106
310
 
107
311
  expect(stacktrace).to eq([
108
312
  { file: "/foo/bar/app/models/user.rb", lineNumber: 1, method: "something" },
@@ -122,7 +326,7 @@ describe Bugsnag::Stacktrace do
122
326
  "abc.rb:1:in `defg'",
123
327
  ]
124
328
 
125
- stacktrace = Bugsnag::Stacktrace.new(backtrace, configuration).to_a
329
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
126
330
 
127
331
  expect(stacktrace).to eq([
128
332
  { code: nil, file: "./foo/bar/baz.rb", lineNumber: 1, method: "something" },
@@ -145,7 +349,7 @@ describe Bugsnag::Stacktrace do
145
349
  "#{dir}/../spec/stacktrace_spec.rb:5:in `something_else'",
146
350
  ]
147
351
 
148
- stacktrace = Bugsnag::Stacktrace.new(backtrace, configuration).to_a
352
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
149
353
 
150
354
  expect(stacktrace).to eq([
151
355
  { file: "#{dir}/spec_helper.rb", lineNumber: 1, method: "something" },
@@ -154,6 +358,48 @@ describe Bugsnag::Stacktrace do
154
358
  { file: "#{dir}/stacktrace_spec.rb", lineNumber: 5, method: "something_else" },
155
359
  ])
156
360
  end
361
+
362
+ it "ignores lines in backtrace that it can't parse" do
363
+ configuration = Bugsnag::Configuration.new
364
+ configuration.send_code = false
365
+
366
+ backtrace = [
367
+ "/foo/bar/baz.rb:2:in `to_s'",
368
+ "this is not formatted correctly :O",
369
+ "/abc/xyz.rb:4:in `to_s'",
370
+ ]
371
+
372
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
373
+
374
+ expect(stacktrace).to eq([
375
+ { file: "/foo/bar/baz.rb", lineNumber: 2, method: "to_s" },
376
+ { file: "/abc/xyz.rb", lineNumber: 4, method: "to_s" },
377
+ ])
378
+ end
379
+
380
+ it "trims Gem prefix from paths" do
381
+ gem_path = Gem.path.first
382
+
383
+ # Sanity check that we have a gem path to strip
384
+ expect(gem_path).not_to be_empty
385
+
386
+ configuration = Bugsnag::Configuration.new
387
+ configuration.send_code = false
388
+
389
+ backtrace = [
390
+ "/foo/bar/baz.rb:2:in `to_s'",
391
+ "#{gem_path}/abc/xyz.rb:4:in `to_s'",
392
+ "/not/gem/path/but/has/gem.rb:6:in `to_s'"
393
+ ]
394
+
395
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
396
+
397
+ expect(stacktrace).to eq([
398
+ { file: "/foo/bar/baz.rb", lineNumber: 2, method: "to_s" },
399
+ { file: "abc/xyz.rb", lineNumber: 4, method: "to_s" },
400
+ { file: "/not/gem/path/but/has/gem.rb", lineNumber: 6, method: "to_s" },
401
+ ])
402
+ end
157
403
  end
158
404
 
159
405
  context "with configurable vendor_path" do
@@ -173,13 +419,13 @@ describe Bugsnag::Stacktrace do
173
419
  end
174
420
 
175
421
  def out_project_trace(stacktrace)
176
- stacktrace.to_a.map do |trace_line|
177
- trace_line[:file] if !trace_line[:inProject]
422
+ stacktrace.map do |trace_line|
423
+ trace_line[:file] unless trace_line[:inProject]
178
424
  end.compact
179
425
  end
180
426
 
181
427
  it "marks vendor/ and .bundle/ as out-project by default" do
182
- stacktrace = Bugsnag::Stacktrace.new(backtrace, configuration)
428
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
183
429
 
184
430
  expect(out_project_trace(stacktrace)).to eq([
185
431
  "vendor/lib/ignore_me.rb",
@@ -189,7 +435,7 @@ describe Bugsnag::Stacktrace do
189
435
 
190
436
  it "allows vendor_path to be configured and filters out backtrace file paths" do
191
437
  configuration.vendor_path = /other_vendor\//
192
- stacktrace = Bugsnag::Stacktrace.new(backtrace, configuration)
438
+ stacktrace = Bugsnag::Stacktrace.process(backtrace, configuration)
193
439
 
194
440
  expect(out_project_trace(stacktrace)).to eq(["other_vendor/lib/dont.rb"])
195
441
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugsnag
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.14.0
4
+ version: 6.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-20 00:00:00.000000000 Z
11
+ date: 2020-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -175,7 +175,9 @@ files:
175
175
  - features/fixtures/plain/app/report_modification/ignore_report.rb
176
176
  - features/fixtures/plain/app/report_modification/initiators/handled_before_notify.rb
177
177
  - features/fixtures/plain/app/report_modification/initiators/handled_block.rb
178
+ - features/fixtures/plain/app/report_modification/initiators/handled_on_error.rb
178
179
  - features/fixtures/plain/app/report_modification/initiators/unhandled_before_notify.rb
180
+ - features/fixtures/plain/app/report_modification/initiators/unhandled_on_error.rb
179
181
  - features/fixtures/plain/app/report_modification/modify_api_key.rb
180
182
  - features/fixtures/plain/app/report_modification/modify_severity.rb
181
183
  - features/fixtures/plain/app/report_modification/remove_user_details.rb
@@ -183,7 +185,9 @@ files:
183
185
  - features/fixtures/plain/app/report_modification/set_user_details.rb
184
186
  - features/fixtures/plain/app/stack_frame_modification/initiators/handled_before_notify.rb
185
187
  - features/fixtures/plain/app/stack_frame_modification/initiators/handled_block.rb
188
+ - features/fixtures/plain/app/stack_frame_modification/initiators/handled_on_error.rb
186
189
  - features/fixtures/plain/app/stack_frame_modification/initiators/unhandled_before_notify.rb
190
+ - features/fixtures/plain/app/stack_frame_modification/initiators/unhandled_on_error.rb
187
191
  - features/fixtures/plain/app/stack_frame_modification/mark_frames_in_project.rb
188
192
  - features/fixtures/plain/app/stack_frame_modification/remove_stack_frame.rb
189
193
  - features/fixtures/plain/app/unhandled/bad_syntax.rb
@@ -600,6 +604,7 @@ files:
600
604
  - features/rails_features/ignore_classes.feature
601
605
  - features/rails_features/meta_data_filters.feature
602
606
  - features/rails_features/mongo_breadcrumbs.feature
607
+ - features/rails_features/on_error.feature
603
608
  - features/rails_features/project_root.feature
604
609
  - features/rails_features/release_stage.feature
605
610
  - features/rails_features/send_code.feature
@@ -615,6 +620,7 @@ files:
615
620
  - lib/bugsnag/breadcrumbs/breadcrumbs.rb
616
621
  - lib/bugsnag/breadcrumbs/validator.rb
617
622
  - lib/bugsnag/cleaner.rb
623
+ - lib/bugsnag/code_extractor.rb
618
624
  - lib/bugsnag/configuration.rb
619
625
  - lib/bugsnag/delivery.rb
620
626
  - lib/bugsnag/delivery/synchronous.rb
@@ -651,6 +657,7 @@ files:
651
657
  - lib/bugsnag/middleware/suggestion_data.rb
652
658
  - lib/bugsnag/middleware/warden_user.rb
653
659
  - lib/bugsnag/middleware_stack.rb
660
+ - lib/bugsnag/on_error_callbacks.rb
654
661
  - lib/bugsnag/report.rb
655
662
  - lib/bugsnag/session_tracker.rb
656
663
  - lib/bugsnag/stacktrace.rb
@@ -663,6 +670,7 @@ files:
663
670
  - spec/breadcrumbs/validator_spec.rb
664
671
  - spec/bugsnag_spec.rb
665
672
  - spec/cleaner_spec.rb
673
+ - spec/code_extractor_spec.rb
666
674
  - spec/configuration_spec.rb
667
675
  - spec/fixtures/apps/rails-initializer-config/Gemfile
668
676
  - spec/fixtures/apps/rails-initializer-config/config.ru
@@ -677,6 +685,10 @@ files:
677
685
  - spec/fixtures/apps/scripts/configure_key.rb
678
686
  - spec/fixtures/apps/scripts/no_config.rb
679
687
  - spec/fixtures/crashes/end_of_file.rb
688
+ - spec/fixtures/crashes/file1.rb
689
+ - spec/fixtures/crashes/file2.rb
690
+ - spec/fixtures/crashes/file_with_long_lines.rb
691
+ - spec/fixtures/crashes/functions.rb
680
692
  - spec/fixtures/crashes/short_file.rb
681
693
  - spec/fixtures/crashes/start_of_file.rb
682
694
  - spec/fixtures/middleware/internal_info_setter.rb
@@ -699,6 +711,7 @@ files:
699
711
  - spec/middleware/exception_meta_data_spec.rb
700
712
  - spec/middleware_spec.rb
701
713
  - spec/middleware_stack_spec.rb
714
+ - spec/on_error_spec.rb
702
715
  - spec/report_spec.rb
703
716
  - spec/session_tracker_spec.rb
704
717
  - spec/spec_helper.rb