bugsnag 6.14.0 → 6.15.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.
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