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
@@ -0,0 +1,25 @@
1
+ module File2
2
+ def self.foo2
3
+ File1.bar1
4
+ end
5
+
6
+ def self.bar2
7
+ File1.baz1
8
+ end
9
+
10
+ def self.baz2
11
+ raise 'uh oh'
12
+ end
13
+
14
+ def self.abc2
15
+ puts 'abc'
16
+ end
17
+
18
+ def self.abcdef2
19
+ puts 'abcdef2'
20
+ end
21
+
22
+ def self.abcdefghi2
23
+ puts 'abcdefghi2'
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ # rubocop:disable Layout/LineLength
2
+ def a_super_long_function_name_that_would_be_really_impractical_to_use_but_luckily_this_is_just_for_a_test_to_prove_we_can_handle_really_long_lines_of_code_that_go_over_200_characters_and_some_more_padding
3
+ puts 'This is a shorter string'
4
+ puts 'A more realistic example of when a line would be really long is long strings such as this one, which extends over the 200 character limit by containing a lot of excess words for padding its length so that it is super long'
5
+ puts 'and another shorter string for comparison'
6
+ end
7
+ # rubocop:enable Layout/LineLength
@@ -0,0 +1,29 @@
1
+ def foo
2
+ bar
3
+ end
4
+
5
+ def bar
6
+ baz
7
+ end
8
+
9
+ def baz
10
+ xyz
11
+ end
12
+
13
+ def xyz
14
+ raise 'uh oh'
15
+ end
16
+
17
+ def abc
18
+ puts 'abc'
19
+ end
20
+
21
+ def abcdef
22
+ puts 'abcdef'
23
+ end
24
+
25
+ def abcdefghi
26
+ puts 'abcdefghi'
27
+ end
28
+
29
+ foo
@@ -1 +1,3 @@
1
+ #
1
2
  raise 'hell'
3
+ #
@@ -0,0 +1,332 @@
1
+ require "spec_helper"
2
+
3
+ describe "on_error callbacks" do
4
+ it "runs callbacks on notify" do
5
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
6
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
7
+
8
+ Bugsnag.add_on_error(callback1)
9
+ Bugsnag.add_on_error(callback2)
10
+
11
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
12
+
13
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
14
+ event = get_event_from_payload(payload)
15
+
16
+ expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
17
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
18
+ end)
19
+ end
20
+
21
+ it "can add callbacks in a configure block" do
22
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
23
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
24
+
25
+ Bugsnag.configure do |config|
26
+ config.add_on_error(callback1)
27
+ config.add_on_error(callback2)
28
+ end
29
+
30
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
31
+
32
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
33
+ event = get_event_from_payload(payload)
34
+
35
+ expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
36
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
37
+ end)
38
+ end
39
+
40
+ it "can remove an already registered callback" do
41
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
42
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
43
+
44
+ Bugsnag.add_on_error(callback1)
45
+ Bugsnag.add_on_error(callback2)
46
+
47
+ Bugsnag.remove_on_error(callback1)
48
+
49
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
50
+
51
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
52
+ event = get_event_from_payload(payload)
53
+
54
+ expect(event["metaData"]["important"]).to be_nil
55
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
56
+ end)
57
+ end
58
+
59
+ it "can remove all registered callbacks" do
60
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
61
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
62
+
63
+ Bugsnag.add_on_error(callback1)
64
+ Bugsnag.add_on_error(callback2)
65
+
66
+ Bugsnag.remove_on_error(callback2)
67
+ Bugsnag.remove_on_error(callback1)
68
+
69
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
70
+
71
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
72
+ event = get_event_from_payload(payload)
73
+
74
+ expect(event["metaData"]["important"]).to be_nil
75
+ expect(event["metaData"]["significant"]).to be_nil
76
+ end)
77
+ end
78
+
79
+ it "does not remove an identical callback if it is not the same Proc" do
80
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
81
+ callback1_duplicate = proc {|report| report.add_tab(:important, { hello: "world" }) }
82
+
83
+ Bugsnag.add_on_error(callback1)
84
+ Bugsnag.remove_on_error(callback1_duplicate)
85
+
86
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
87
+
88
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
89
+ event = get_event_from_payload(payload)
90
+
91
+ expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
92
+ end)
93
+ end
94
+
95
+ it "can re-add callbacks that have previously been removed" do
96
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
97
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
98
+
99
+ Bugsnag.add_on_error(callback1)
100
+ Bugsnag.add_on_error(callback2)
101
+
102
+ Bugsnag.remove_on_error(callback1)
103
+
104
+ Bugsnag.add_on_error(callback1)
105
+
106
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
107
+
108
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
109
+ event = get_event_from_payload(payload)
110
+
111
+ expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
112
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
113
+ end)
114
+ end
115
+
116
+ it "will only add a callback once" do
117
+ called_count = 0
118
+
119
+ callback = proc do |report|
120
+ called_count += 1
121
+
122
+ report.add_tab(:important, { called: called_count })
123
+ end
124
+
125
+ 1.upto(10) do |i|
126
+ Bugsnag.add_on_error(callback)
127
+ end
128
+
129
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
130
+
131
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
132
+ event = get_event_from_payload(payload)
133
+
134
+ expect(called_count).to be(1)
135
+ expect(event["metaData"]["important"]).to eq({ "called" => 1 })
136
+ end)
137
+ end
138
+
139
+ it "will ignore the report and stop calling callbacks if one returns false" do
140
+ logger = spy('logger')
141
+ Bugsnag.configuration.logger = logger
142
+
143
+ called_count = 0
144
+
145
+ callback1 = proc { called_count += 1 }
146
+ callback2 = proc { called_count += 1 }
147
+ callback3 = proc { false }
148
+ callback4 = proc { called_count += 1 }
149
+ callback5 = proc { called_count += 1 }
150
+
151
+ Bugsnag.add_on_error(callback1)
152
+ Bugsnag.add_on_error(callback2)
153
+ Bugsnag.add_on_error(callback3)
154
+ Bugsnag.add_on_error(callback4)
155
+ Bugsnag.add_on_error(callback5)
156
+
157
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
158
+
159
+ expect(Bugsnag).not_to have_sent_notification
160
+ expect(called_count).to be(2)
161
+
162
+ expect(logger).to have_received(:debug).with("[Bugsnag]") do |&block|
163
+ expect(block.call).to eq("Not notifying RuntimeError due to ignore being signified in user provided middleware")
164
+ end
165
+ end
166
+
167
+ it "callbacks are called in the same order they are added (FIFO)" do
168
+ callback1 = proc do |report|
169
+ expect(report.meta_data[:important]).to be_nil
170
+
171
+ report.add_tab(:important, { magic_number: 9 })
172
+ end
173
+
174
+ callback2 = proc do |report|
175
+ expect(report.meta_data[:important]).to eq({ magic_number: 9 })
176
+
177
+ report.add_tab(:important, { magic_number: 99 })
178
+ end
179
+
180
+ callback3 = proc do |report|
181
+ expect(report.meta_data[:important]).to eq({ magic_number: 99 })
182
+
183
+ report.add_tab(:important, { magic_number: 999 })
184
+ end
185
+
186
+ Bugsnag.add_on_error(callback1)
187
+ Bugsnag.add_on_error(callback2)
188
+ Bugsnag.add_on_error(callback3)
189
+
190
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
191
+
192
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
193
+ event = get_event_from_payload(payload)
194
+
195
+ expect(event["metaData"]["important"]).to eq({ "magic_number" => 999 })
196
+ end)
197
+ end
198
+
199
+ it "callbacks continue being called after a callback raises" do
200
+ logger = spy('logger')
201
+ Bugsnag.configuration.logger = logger
202
+
203
+ callback1 = proc {|report| report.add_tab(:important, { a: "b" }) }
204
+ callback2 = proc {|_report| raise "bad things" }
205
+ callback3 = proc {|report| report.add_tab(:important, { c: "d" }) }
206
+
207
+ Bugsnag.add_on_error(callback1)
208
+ Bugsnag.add_on_error(callback2)
209
+ Bugsnag.add_on_error(callback3)
210
+
211
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
212
+
213
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
214
+ event = get_event_from_payload(payload)
215
+
216
+ expect(event["metaData"]["important"]).to eq({ "a" => "b", "c" => "d" })
217
+ end)
218
+
219
+ message_index = 0
220
+ expected_messages = [
221
+ /^Error occurred in on_error callback: 'bad things'$/,
222
+ /^on_error callback stacktrace:/
223
+ ]
224
+
225
+ expect(logger).to have_received(:warn).with("[Bugsnag]").twice do |&block|
226
+ expect(block.call).to match(expected_messages[message_index])
227
+ message_index += 1
228
+ end
229
+ end
230
+
231
+ it "runs callbacks even if no other middleware is registered" do
232
+ # Reset the middleware stack so any default middleware are removed
233
+ Bugsnag.configuration.middleware = Bugsnag::MiddlewareStack.new
234
+
235
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
236
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
237
+
238
+ Bugsnag.add_on_error(callback1)
239
+ Bugsnag.add_on_error(callback2)
240
+
241
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
242
+
243
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
244
+ event = get_event_from_payload(payload)
245
+
246
+ expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
247
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
248
+ end)
249
+ end
250
+
251
+ describe "using callbacks across threads" do
252
+ it "runs callbacks that are added in different threads" do
253
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
254
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
255
+ callback3 = proc {|report| report.add_tab(:crucial, { magic_number: 999 }) }
256
+
257
+ Bugsnag.add_on_error(callback1)
258
+
259
+ threads = [
260
+ Thread.new { Bugsnag.add_on_error(callback2) },
261
+ Thread.new { Bugsnag.add_on_error(callback3) }
262
+ ]
263
+
264
+ threads.each(&:join)
265
+
266
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
267
+
268
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
269
+ event = get_event_from_payload(payload)
270
+
271
+ expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
272
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
273
+ expect(event["metaData"]["crucial"]).to eq({ "magic_number" => 999 })
274
+ end)
275
+ end
276
+
277
+ it "can remove callbacks that are added in different threads" do
278
+ callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
279
+ callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
280
+
281
+ # We need to create & join these one at a time so that callback1 has
282
+ # definitely been added before it is removed, otherwise this test will fail
283
+ # at random
284
+ Thread.new { Bugsnag.add_on_error(callback1) }.join
285
+ Thread.new { Bugsnag.remove_on_error(callback1) }.join
286
+ Thread.new { Bugsnag.add_on_error(callback2) }.join
287
+
288
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
289
+
290
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
291
+ event = get_event_from_payload(payload)
292
+
293
+ expect(event["metaData"]["important"]).to be_nil
294
+ expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
295
+ end)
296
+ end
297
+
298
+ it "callbacks are called in FIFO order when added in separate threads" do
299
+ callback1 = proc do |report|
300
+ expect(report.meta_data[:important]).to be_nil
301
+
302
+ report.add_tab(:important, { magic_number: 9 })
303
+ end
304
+
305
+ callback2 = proc do |report|
306
+ expect(report.meta_data[:important]).to eq({ magic_number: 9 })
307
+
308
+ report.add_tab(:important, { magic_number: 99 })
309
+ end
310
+
311
+ callback3 = proc do |report|
312
+ expect(report.meta_data[:important]).to eq({ magic_number: 99 })
313
+
314
+ report.add_tab(:important, { magic_number: 999 })
315
+ end
316
+
317
+ # As above, we need to create & join these one at a time so that each
318
+ # callback is added in sequence
319
+ Thread.new { Bugsnag.add_on_error(callback1) }.join
320
+ Thread.new { Bugsnag.add_on_error(callback2) }.join
321
+ Thread.new { Bugsnag.add_on_error(callback3) }.join
322
+
323
+ Bugsnag.notify(RuntimeError.new("Oh no!"))
324
+
325
+ expect(Bugsnag).to(have_sent_notification do |payload, _headers|
326
+ event = get_event_from_payload(payload)
327
+
328
+ expect(event["metaData"]["important"]).to eq({ "magic_number" => 999 })
329
+ end)
330
+ end
331
+ end
332
+ end
@@ -1373,27 +1373,30 @@ describe Bugsnag::Report do
1373
1373
  notify_test_exception
1374
1374
  expect(Bugsnag).to have_sent_notification{ |payload, headers|
1375
1375
  exception = get_exception_from_payload(payload)
1376
+
1376
1377
  bugsnag_count = 0
1378
+
1377
1379
  exception["stacktrace"].each do |frame|
1378
1380
  if /.*lib\/bugsnag.*\.rb/.match(frame["file"])
1379
1381
  bugsnag_count += 1
1380
1382
  expect(frame["inProject"]).to be_nil
1381
1383
  end
1382
1384
  end
1383
- # 7 is used here as the called bugsnag frames for a `notify` call should be:
1385
+
1386
+ # 6 is used here as the called bugsnag frames for a `notify` call should be:
1384
1387
  # - Bugsnag.notify
1385
1388
  # - Report.new
1386
1389
  # - Report.initialize
1387
1390
  # - Report.generate_exceptions_list
1388
1391
  # - Report.generate_exceptions_list | raw_exceptions.map
1389
1392
  # - Report.generate_exceptions_list | raw_exceptions.map | block
1390
- # - Report.generate_exceptions_list | raw_exceptions.map | block | Stacktrace.new
1391
- # However, JRUBY does not include the two `new` frames, resulting in 5 bugsnag frames
1393
+ # However, JRUBY does not include the `Report.new` frame, resulting in 5 bugsnag frames
1392
1394
  if defined?(JRUBY_VERSION)
1393
1395
  frame_count = 5
1394
1396
  else
1395
- frame_count = 7
1397
+ frame_count = 6
1396
1398
  end
1399
+
1397
1400
  expect(bugsnag_count).to equal frame_count
1398
1401
  }
1399
1402
  end
@@ -30,6 +30,14 @@ def get_exception_from_payload(payload)
30
30
  event["exceptions"].last
31
31
  end
32
32
 
33
+ def get_code_from_payload(payload, index = 0)
34
+ exception = get_exception_from_payload(payload)
35
+
36
+ expect(exception["stacktrace"].size).to be > index
37
+
38
+ exception["stacktrace"][index]["code"]
39
+ end
40
+
33
41
  def notify_test_exception(*args)
34
42
  Bugsnag.notify(RuntimeError.new("test message"), *args)
35
43
  end