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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37ddadee8c92423e803ed8d9be43b564c79110ea9ddf8dd4180a9a3b1080e575
4
- data.tar.gz: 887d7017da4d23fe00515109c8c791c2e4d205e5968faaa3178d522ab5a7c51c
3
+ metadata.gz: 1a4f6c896c7046dba3d9db2adfa32e232232e3a7923cb9984321ee3a8b09b6e3
4
+ data.tar.gz: f6b27c0b50846c1a3e012bb80bccbe5ad6663257c8de4e300742e7af4b065b3f
5
5
  SHA512:
6
- metadata.gz: 0a4808f2f7f73728ebab03d434f3ddccc38ceb743d986adf56225725ebdeee84c9ec63c4a2334078fbef44aa084b362468ab6cc98f2bcf8bc6ada991db44099a
7
- data.tar.gz: ad4ba7af2a2aae7360cf111507dbaec30e0a6e668b606c111e231fc75683556ff14e0376673c50f45231dbcec5f59ff0649cf2e89c85e8cedf7e14b9b4fc196d
6
+ metadata.gz: d51d64bbf0b2cc094742a8a207702e963c33eac9e18f58f2348f3d9e6475a1bd0c43dc44baf139bb976823bfabd8b3ff5bab261ae3dababd46f083b7539602f4
7
+ data.tar.gz: 3621690efd266608313b000f7acff6df354380457e9b78e448290573e97dd0b17beb68afa5efff96fdcc7b21b39d2d292fddc38082eae7bece24dd1f2d457f0d
@@ -1,6 +1,23 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## 6.15.0 (27 July 2020)
5
+
6
+ ### Enhancements
7
+
8
+ * Add `on_error` callbacks to replace `before_notify_callbacks`
9
+ | [#608](https://github.com/bugsnag/bugsnag-ruby/pull/608)
10
+
11
+ * Improve performance when extracting code from files in stacktraces
12
+ | [#604](https://github.com/bugsnag/bugsnag-ruby/pull/604)
13
+
14
+ * Reduce memory use when session tracking is disabled
15
+ | [#606](https://github.com/bugsnag/bugsnag-ruby/pull/606)
16
+
17
+ ### Deprecated
18
+
19
+ * `before_notify_callbacks` have been deprecated in favour of `on_error` and will be removed in the next major release
20
+
4
21
  ## 6.14.0 (20 July 2020)
5
22
 
6
23
  ### Enhancements
data/VERSION CHANGED
@@ -1 +1 @@
1
- 6.14.0
1
+ 6.15.0
@@ -120,6 +120,7 @@ services:
120
120
  - BUGSNAG_TIMEOUT
121
121
  - CALLBACK_INITIATOR
122
122
  - SQL_ONLY_BREADCRUMBS
123
+ - ADD_ON_ERROR
123
124
  - USE_DEFAULT_AUTO_CAPTURE_SESSIONS
124
125
  restart: "no"
125
126
 
@@ -155,6 +156,7 @@ services:
155
156
  - BUGSNAG_TIMEOUT
156
157
  - CALLBACK_INITIATOR
157
158
  - SQL_ONLY_BREADCRUMBS
159
+ - ADD_ON_ERROR
158
160
  - USE_DEFAULT_AUTO_CAPTURE_SESSIONS
159
161
  restart: "no"
160
162
 
@@ -190,6 +192,7 @@ services:
190
192
  - BUGSNAG_TIMEOUT
191
193
  - CALLBACK_INITIATOR
192
194
  - SQL_ONLY_BREADCRUMBS
195
+ - ADD_ON_ERROR
193
196
  - USE_DEFAULT_AUTO_CAPTURE_SESSIONS
194
197
  restart: "no"
195
198
 
@@ -225,6 +228,7 @@ services:
225
228
  - BUGSNAG_TIMEOUT
226
229
  - CALLBACK_INITIATOR
227
230
  - SQL_ONLY_BREADCRUMBS
231
+ - ADD_ON_ERROR
228
232
  - USE_DEFAULT_AUTO_CAPTURE_SESSIONS
229
233
  restart: "no"
230
234
  networks:
@@ -296,4 +300,4 @@ services:
296
300
 
297
301
  networks:
298
302
  default:
299
- name: ${NETWORK_NAME}
303
+ name: ${NETWORK_NAME}
@@ -0,0 +1,10 @@
1
+ require 'bugsnag'
2
+ require './app'
3
+
4
+ configure_basics
5
+
6
+ def run(callback)
7
+ Bugsnag.add_on_error(callback)
8
+
9
+ Bugsnag.notify(RuntimeError.new("Oh no"))
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'bugsnag'
2
+ require './app'
3
+
4
+ configure_basics
5
+ add_at_exit
6
+
7
+ def run(callback)
8
+ Bugsnag.add_on_error(callback)
9
+
10
+ raise RuntimeError.new "Oh no"
11
+ end
@@ -0,0 +1,29 @@
1
+ require 'bugsnag'
2
+ require './app'
3
+
4
+ configure_basics
5
+
6
+ def run(callback)
7
+ Bugsnag.add_on_error(callback)
8
+ step_one
9
+ end
10
+
11
+ def step_one
12
+ step_two
13
+ end
14
+
15
+ def step_two
16
+ step_three
17
+ end
18
+
19
+ def step_three
20
+ crash
21
+ end
22
+
23
+ def crash
24
+ begin
25
+ "Test".insrt(-1, "!")
26
+ rescue Exception => e
27
+ Bugsnag.notify(e)
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ require 'bugsnag'
2
+ require './app'
3
+
4
+ configure_basics
5
+ add_at_exit
6
+
7
+ def run(callback)
8
+ Bugsnag.add_on_error(callback)
9
+ step_one
10
+ end
11
+
12
+ def step_one
13
+ step_two
14
+ end
15
+
16
+ def step_two
17
+ step_three
18
+ end
19
+
20
+ def step_three
21
+ crash
22
+ end
23
+
24
+ def crash
25
+ raise RuntimeError.new "Oh no"
26
+ end
@@ -18,4 +18,12 @@ Bugsnag.configure do |config|
18
18
  breadcrumb.ignore! unless breadcrumb.meta_data[:event_name] == "sql.active_record" && breadcrumb.meta_data[:name] == "User Load"
19
19
  end
20
20
  end
21
+
22
+ if ENV["ADD_ON_ERROR"] == "true"
23
+ config.add_on_error(proc do |report|
24
+ report.add_tab(:on_error, {
25
+ source: report.unhandled ? 'on_error unhandled' : 'on_error handled'
26
+ })
27
+ end)
28
+ end
21
29
  end
@@ -18,4 +18,12 @@ Bugsnag.configure do |config|
18
18
  breadcrumb.ignore! unless breadcrumb.meta_data[:event_name] == "sql.active_record" && breadcrumb.meta_data[:name] == "User Load"
19
19
  end
20
20
  end
21
+
22
+ if ENV["ADD_ON_ERROR"] == "true"
23
+ config.add_on_error(proc do |report|
24
+ report.add_tab(:on_error, {
25
+ source: report.unhandled ? 'on_error unhandled' : 'on_error handled'
26
+ })
27
+ end)
28
+ end
21
29
  end
@@ -18,4 +18,12 @@ Bugsnag.configure do |config|
18
18
  breadcrumb.ignore! unless breadcrumb.meta_data[:event_name] == "sql.active_record" && breadcrumb.meta_data[:name] == "User Load"
19
19
  end
20
20
  end
21
+
22
+ if ENV["ADD_ON_ERROR"] == "true"
23
+ config.add_on_error(proc do |report|
24
+ report.add_tab(:on_error, {
25
+ source: report.unhandled ? 'on_error unhandled' : 'on_error handled'
26
+ })
27
+ end)
28
+ end
21
29
  end
@@ -18,4 +18,12 @@ Bugsnag.configure do |config|
18
18
  breadcrumb.ignore! unless breadcrumb.meta_data[:event_name] == "sql.active_record" && breadcrumb.meta_data[:name] == "User Load"
19
19
  end
20
20
  end
21
+
22
+ if ENV["ADD_ON_ERROR"] == "true"
23
+ config.add_on_error(proc do |report|
24
+ report.add_tab(:on_error, {
25
+ source: report.unhandled ? 'on_error unhandled' : 'on_error handled'
26
+ })
27
+ end)
28
+ end
21
29
  end
@@ -15,6 +15,8 @@ Scenario Outline: Metadata can be added to a report using add_tab
15
15
  | handled_before_notify |
16
16
  | handled_block |
17
17
  | unhandled_before_notify |
18
+ | handled_on_error |
19
+ | unhandled_on_error |
18
20
 
19
21
  Scenario Outline: Metadata can be added to an existing tab using add_tab
20
22
  Given I set environment variable "CALLBACK_INITIATOR" to "<initiator>"
@@ -33,6 +35,8 @@ Scenario Outline: Metadata can be added to an existing tab using add_tab
33
35
  | handled_before_notify |
34
36
  | handled_block |
35
37
  | unhandled_before_notify |
38
+ | handled_on_error |
39
+ | unhandled_on_error |
36
40
 
37
41
  Scenario Outline: Metadata can be overwritten using add_tab
38
42
  Given I set environment variable "CALLBACK_INITIATOR" to "<initiator>"
@@ -46,4 +50,6 @@ Scenario Outline: Metadata can be overwritten using add_tab
46
50
  | initiator |
47
51
  | handled_before_notify |
48
52
  | handled_block |
49
- | unhandled_before_notify |
53
+ | unhandled_before_notify |
54
+ | handled_on_error |
55
+ | unhandled_on_error |
@@ -10,3 +10,5 @@ Scenario Outline: A reports severity can be modified
10
10
  | initiator |
11
11
  | handled_before_notify |
12
12
  | unhandled_before_notify |
13
+ | handled_on_error |
14
+ | unhandled_on_error |
@@ -11,4 +11,6 @@ Scenario Outline: A report can have its api_key modified
11
11
  | initiator |
12
12
  | handled_before_notify |
13
13
  | handled_block |
14
- | unhandled_before_notify |
14
+ | unhandled_before_notify |
15
+ | handled_on_error |
16
+ | unhandled_on_error |
@@ -13,3 +13,5 @@ Scenario Outline: A reports severity can be modified
13
13
  | handled_before_notify |
14
14
  | handled_block |
15
15
  | unhandled_before_notify |
16
+ | handled_on_error |
17
+ | unhandled_on_error |
@@ -12,6 +12,8 @@ Scenario Outline: Stack frames can be removed
12
12
  | initiator | lineNumber |
13
13
  | handled_before_notify | 20 |
14
14
  | unhandled_before_notify | 21 |
15
+ | handled_on_error | 20 |
16
+ | unhandled_on_error | 21 |
15
17
 
16
18
  Scenario: Stack frames can be removed from a notified string
17
19
  Given I set environment variable "CALLBACK_INITIATOR" to "handled_block"
@@ -36,6 +38,8 @@ Scenario Outline: Stack frames can be marked as in project
36
38
  | initiator |
37
39
  | handled_before_notify |
38
40
  | unhandled_before_notify |
41
+ | handled_on_error |
42
+ | unhandled_on_error |
39
43
 
40
44
  Scenario: Stack frames can be marked as in project with a handled string
41
45
  Given I set environment variable "CALLBACK_INITIATOR" to "handled_block"
@@ -14,6 +14,8 @@ Scenario Outline: A report can have a user name, email, and id set
14
14
  | handled_before_notify |
15
15
  | handled_block |
16
16
  | unhandled_before_notify |
17
+ | handled_on_error |
18
+ | unhandled_on_error |
17
19
 
18
20
  Scenario Outline: A report can have custom info set
19
21
  Given I set environment variable "CALLBACK_INITIATOR" to "<initiator>"
@@ -30,6 +32,8 @@ Scenario Outline: A report can have custom info set
30
32
  | handled_before_notify |
31
33
  | handled_block |
32
34
  | unhandled_before_notify |
35
+ | handled_on_error |
36
+ | unhandled_on_error |
33
37
 
34
38
  Scenario Outline: A report can have its user info removed
35
39
  Given I set environment variable "CALLBACK_INITIATOR" to "<initiator>"
@@ -42,4 +46,6 @@ Scenario Outline: A report can have its user info removed
42
46
  | initiator |
43
47
  | handled_before_notify |
44
48
  | handled_block |
45
- | unhandled_before_notify |
49
+ | unhandled_before_notify |
50
+ | handled_on_error |
51
+ | unhandled_on_error |
@@ -0,0 +1,29 @@
1
+ Feature: On error callbacks
2
+
3
+ @rails3 @rails4 @rails5 @rails6
4
+ Scenario: Rails on_error works on handled errors
5
+ Given I set environment variable "ADD_ON_ERROR" to "true"
6
+ And I start the rails service
7
+ When I navigate to the route "/handled/unthrown" on the rails app
8
+ And I wait to receive a request
9
+ Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier"
10
+ And the exception "errorClass" equals "RuntimeError"
11
+ And the exception "message" starts with "handled unthrown error"
12
+ And the event "unhandled" is false
13
+ And the event "app.type" equals "rails"
14
+ And the event "metaData.request.url" ends with "/handled/unthrown"
15
+ And the event "metaData.on_error.source" equals "on_error handled"
16
+
17
+ @rails3 @rails4 @rails5 @rails6
18
+ Scenario: Rails on_error works on unhandled errors
19
+ Given I set environment variable "ADD_ON_ERROR" to "true"
20
+ And I start the rails service
21
+ When I navigate to the route "/unhandled/error" on the rails app
22
+ And I wait to receive a request
23
+ Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier"
24
+ And the exception "errorClass" equals "NameError"
25
+ And the exception "message" starts with "undefined local variable or method `generate_unhandled_error' for #<UnhandledController"
26
+ And the event "unhandled" is true
27
+ And the event "app.type" equals "rails"
28
+ And the event "metaData.request.url" ends with "/unhandled/error"
29
+ And the event "metaData.on_error.source" equals "on_error unhandled"
@@ -48,6 +48,12 @@ module Bugsnag
48
48
  def configure(validate_api_key=true)
49
49
  yield(configuration) if block_given?
50
50
 
51
+ # Create the session tracker if sessions are enabled to avoid the overhead
52
+ # of creating it on the first request. We skip this if we're not validating
53
+ # the API key as we use this internally before the user's configure block
54
+ # has run, so we don't know if sessions are enabled yet.
55
+ session_tracker if validate_api_key && configuration.auto_capture_sessions
56
+
51
57
  check_key_valid if validate_api_key
52
58
  check_endpoint_setup
53
59
 
@@ -169,6 +175,8 @@ module Bugsnag
169
175
  # Allow access to "before notify" callbacks as an array.
170
176
  #
171
177
  # These callbacks will be called whenever an error notification is being made.
178
+ #
179
+ # @deprecated Use {Bugsnag#add_on_error} instead
172
180
  def before_notify_callbacks
173
181
  Bugsnag.configuration.request_data[:before_callbacks] ||= []
174
182
  end
@@ -227,6 +235,33 @@ module Bugsnag
227
235
  configuration.breadcrumbs << breadcrumb unless breadcrumb.ignore?
228
236
  end
229
237
 
238
+ ##
239
+ # Add the given callback to the list of on_error callbacks
240
+ #
241
+ # The on_error callbacks will be called when an error is captured or reported
242
+ # and are passed a {Bugsnag::Report} object
243
+ #
244
+ # Returning false from an on_error callback will cause the error to be ignored
245
+ # and will prevent any remaining callbacks from being called
246
+ #
247
+ # @param callback [Proc]
248
+ # @return [void]
249
+ def add_on_error(callback)
250
+ configuration.add_on_error(callback)
251
+ end
252
+
253
+ ##
254
+ # Remove the given callback from the list of on_error callbacks
255
+ #
256
+ # Note that this must be the same Proc instance that was passed to
257
+ # {Bugsnag#add_on_error}, otherwise it will not be removed
258
+ #
259
+ # @param callback [Proc]
260
+ # @return [void]
261
+ def remove_on_error(callback)
262
+ configuration.remove_on_error(callback)
263
+ end
264
+
230
265
  ##
231
266
  # Returns the client's Cleaner object, or creates one if not yet created.
232
267
  #
@@ -0,0 +1,137 @@
1
+ module Bugsnag
2
+ # @api private
3
+ class CodeExtractor
4
+ MAXIMUM_LINES_TO_KEEP = 7
5
+
6
+ ##
7
+ # @param configuration [Configuration]
8
+ def initialize(configuration)
9
+ @files = {}
10
+ @configuration = configuration
11
+ end
12
+
13
+ ##
14
+ # Add a file and its corresponding trace hash to be processed.
15
+ #
16
+ # @param path [String] The full path to the file
17
+ # @param trace [Hash]
18
+ # @return [void]
19
+ def add_file(path, trace)
20
+ # If the file doesn't exist we can't extract code from it, so we can skip
21
+ # this file entirely
22
+ unless File.exist?(path)
23
+ trace[:code] = nil
24
+
25
+ return
26
+ end
27
+
28
+ @files[path] ||= []
29
+ @files[path].push(trace)
30
+
31
+ # Record the line numbers we want to fetch for this trace
32
+ # We grab extra lines so that we can compensate if the error is on the
33
+ # first or last line of a file
34
+ first_line_number = trace[:lineNumber] - MAXIMUM_LINES_TO_KEEP
35
+
36
+ trace[:first_line_number] = first_line_number < 1 ? 1 : first_line_number
37
+ trace[:last_line_number] = trace[:lineNumber] + MAXIMUM_LINES_TO_KEEP
38
+ end
39
+
40
+ ##
41
+ # Add the code to the hashes that were given in {#add_file} by modifying
42
+ # them in-place. They will have a new ':code' key containing a hash of line
43
+ # number => string of code for that line
44
+ #
45
+ # @return [void]
46
+ def extract!
47
+ @files.each do |path, traces|
48
+ begin
49
+ line_numbers = Set.new
50
+
51
+ traces.each do |trace|
52
+ trace[:first_line_number].upto(trace[:last_line_number]) do |line_number|
53
+ line_numbers << line_number
54
+ end
55
+ end
56
+
57
+ extract_from(path, traces, line_numbers)
58
+ rescue StandardError => e
59
+ # Clean up after ourselves
60
+ traces.each do |trace|
61
+ trace[:code] ||= nil
62
+ trace.delete(:first_line_number)
63
+ trace.delete(:last_line_number)
64
+ end
65
+
66
+ @configuration.warn("Error extracting code: #{e.inspect}")
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ ##
74
+ # @param path [String]
75
+ # @param traces [Array<Hash>]
76
+ # @param line_numbers [Set<Integer>]
77
+ # @return [void]
78
+ def extract_from(path, traces, line_numbers)
79
+ code = {}
80
+
81
+ File.open(path) do |file|
82
+ current_line_number = 0
83
+
84
+ file.each_line do |line|
85
+ current_line_number += 1
86
+
87
+ next unless line_numbers.include?(current_line_number)
88
+
89
+ code[current_line_number] = line[0...200].rstrip
90
+ end
91
+ end
92
+
93
+ associate_code_with_trace(code, traces)
94
+ end
95
+
96
+ ##
97
+ # @param code [Hash{Integer => String}]
98
+ # @param traces [Array<Hash>]
99
+ # @return [void]
100
+ def associate_code_with_trace(code, traces)
101
+ traces.each do |trace|
102
+ trace[:code] = {}
103
+
104
+ code.each do |line_number, line|
105
+ # If we've gone past the last line we care about, we can stop iterating
106
+ break if line_number > trace[:last_line_number]
107
+
108
+ # Skip lines that aren't in the range we want
109
+ next unless line_number >= trace[:first_line_number]
110
+
111
+ trace[:code][line_number] = line
112
+ end
113
+
114
+ trim_excess_lines(trace[:code], trace[:lineNumber])
115
+ trace.delete(:first_line_number)
116
+ trace.delete(:last_line_number)
117
+ end
118
+ end
119
+
120
+ ##
121
+ # @param code [Hash{Integer => String}]
122
+ # @param line_number [Integer]
123
+ # @return [void]
124
+ def trim_excess_lines(code, line_number)
125
+ while code.length > MAXIMUM_LINES_TO_KEEP
126
+ last_line = code.keys.max
127
+ first_line = code.keys.min
128
+
129
+ if (last_line - line_number) > (line_number - first_line)
130
+ code.delete(last_line)
131
+ else
132
+ code.delete(first_line)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end