bugsnag 6.13.1 → 6.14.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.
@@ -3,6 +3,7 @@ require "socket"
3
3
  require "logger"
4
4
  require "bugsnag/middleware_stack"
5
5
  require "bugsnag/middleware/callbacks"
6
+ require "bugsnag/middleware/discard_error_class"
6
7
  require "bugsnag/middleware/exception_meta_data"
7
8
  require "bugsnag/middleware/ignore_error_class"
8
9
  require "bugsnag/middleware/suggestion_data"
@@ -35,9 +36,13 @@ module Bugsnag
35
36
  attr_accessor :timeout
36
37
  attr_accessor :hostname
37
38
  attr_accessor :runtime_versions
38
- attr_accessor :ignore_classes
39
+ attr_accessor :discard_classes
39
40
  attr_accessor :auto_capture_sessions
40
41
 
42
+ ##
43
+ # @deprecated Use {#discard_classes} instead
44
+ attr_accessor :ignore_classes
45
+
41
46
  ##
42
47
  # @return [String] URL error notifications will be delivered to
43
48
  attr_reader :notify_endpoint
@@ -67,6 +72,10 @@ module Bugsnag
67
72
  # @return [Regexp] matching file paths out of project
68
73
  attr_accessor :vendor_path
69
74
 
75
+ ##
76
+ # @return [Array]
77
+ attr_reader :scopes_to_filter
78
+
70
79
  API_KEY_REGEX = /[0-9a-f]{32}/i
71
80
  THREAD_LOCAL_NAME = "bugsnag_req_data"
72
81
 
@@ -86,7 +95,9 @@ module Bugsnag
86
95
  DEFAULT_MAX_BREADCRUMBS = 25
87
96
 
88
97
  # Path to vendored code. Used to mark file paths as out of project.
89
- DEFAULT_VENDOR_PATH = %r{^(vendor\/|\.bundle\/)}
98
+ DEFAULT_VENDOR_PATH = %r{^(vendor/|\.bundle/)}
99
+
100
+ DEFAULT_SCOPES_TO_FILTER = ['events.metaData', 'events.breadcrumbs.metaData'].freeze
90
101
 
91
102
  alias :track_sessions :auto_capture_sessions
92
103
  alias :track_sessions= :auto_capture_sessions=
@@ -99,6 +110,7 @@ module Bugsnag
99
110
  self.send_environment = false
100
111
  self.send_code = true
101
112
  self.meta_data_filters = Set.new(DEFAULT_META_DATA_FILTERS)
113
+ self.scopes_to_filter = DEFAULT_SCOPES_TO_FILTER
102
114
  self.hostname = default_hostname
103
115
  self.runtime_versions = {}
104
116
  self.runtime_versions["ruby"] = RUBY_VERSION
@@ -122,7 +134,10 @@ module Bugsnag
122
134
 
123
135
  # SystemExit and SignalException are common Exception types seen with
124
136
  # successful exits and are not automatically reported to Bugsnag
137
+ # TODO move these defaults into `discard_classes` when `ignore_classes`
138
+ # is removed
125
139
  self.ignore_classes = Set.new([SystemExit, SignalException])
140
+ self.discard_classes = Set.new([])
126
141
 
127
142
  # Read the API key from the environment
128
143
  self.api_key = ENV["BUGSNAG_API_KEY"]
@@ -147,6 +162,7 @@ module Bugsnag
147
162
  # Configure the bugsnag middleware stack
148
163
  self.internal_middleware = Bugsnag::MiddlewareStack.new
149
164
  self.internal_middleware.use Bugsnag::Middleware::ExceptionMetaData
165
+ self.internal_middleware.use Bugsnag::Middleware::DiscardErrorClass
150
166
  self.internal_middleware.use Bugsnag::Middleware::IgnoreErrorClass
151
167
  self.internal_middleware.use Bugsnag::Middleware::SuggestionData
152
168
  self.internal_middleware.use Bugsnag::Middleware::ClassifyError
@@ -304,6 +320,8 @@ module Bugsnag
304
320
 
305
321
  private
306
322
 
323
+ attr_writer :scopes_to_filter
324
+
307
325
  PROG_NAME = "[Bugsnag]"
308
326
 
309
327
  def default_hostname
@@ -17,12 +17,10 @@ module Bugsnag
17
17
  def self.trim_if_needed(value)
18
18
  value = "" if value.nil?
19
19
 
20
- # Sanitize object
21
- sanitized_value = Bugsnag::Cleaner.clean_object_encoding(value)
22
- return sanitized_value unless payload_too_long?(sanitized_value)
20
+ return value unless payload_too_long?(value)
23
21
 
24
22
  # Trim metadata
25
- reduced_value = trim_metadata(sanitized_value)
23
+ reduced_value = trim_metadata(value)
26
24
  return reduced_value unless payload_too_long?(reduced_value)
27
25
 
28
26
  # Trim code from stacktrace
@@ -0,0 +1,30 @@
1
+ module Bugsnag::Middleware
2
+ ##
3
+ # Determines if the exception should be ignored based on the configured
4
+ # `discard_classes`
5
+ class DiscardErrorClass
6
+ ##
7
+ # @param middleware [#call] The next middleware to call
8
+ def initialize(middleware)
9
+ @middleware = middleware
10
+ end
11
+
12
+ ##
13
+ # @param report [Report]
14
+ def call(report)
15
+ should_discard = report.raw_exceptions.any? do |ex|
16
+ report.configuration.discard_classes.any? do |to_ignore|
17
+ case to_ignore
18
+ when String then to_ignore == ex.class.name
19
+ when Regexp then to_ignore =~ ex.class.name
20
+ else false
21
+ end
22
+ end
23
+ end
24
+
25
+ report.ignore! if should_discard
26
+
27
+ @middleware.call(report)
28
+ end
29
+ end
30
+ end
@@ -2,6 +2,8 @@ module Bugsnag::Middleware
2
2
  ##
3
3
  # Determines if the exception should be ignored based on the configured
4
4
  # `ignore_classes`
5
+ #
6
+ # @deprecated Use {DiscardErrorClass} instead
5
7
  class IgnoreErrorClass
6
8
  def initialize(bugsnag)
7
9
  @bugsnag = bugsnag
@@ -28,18 +28,16 @@ module Bugsnag::Middleware
28
28
  url = "#{request.scheme}://#{request.host}"
29
29
  url << ":#{request.port}" unless [80, 443].include?(request.port)
30
30
 
31
- cleaner = Bugsnag::Cleaner.new(report.configuration.meta_data_filters)
32
-
33
31
  # If app is passed a bad URL, this code will crash attempting to clean it
34
32
  begin
35
- url << cleaner.clean_url(request.fullpath)
33
+ url << Bugsnag.cleaner.clean_url(request.fullpath)
36
34
  rescue StandardError => stde
37
35
  Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.fullpath: #{stde}"
38
36
  end
39
37
 
40
38
  referer = nil
41
39
  begin
42
- referer = cleaner.clean_url(request.referer) if request.referer
40
+ referer = Bugsnag.cleaner.clean_url(request.referer) if request.referer
43
41
  rescue StandardError => stde
44
42
  Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.referer: #{stde}"
45
43
  end
@@ -97,6 +97,7 @@ module Bugsnag
97
97
  releaseStage: release_stage,
98
98
  type: app_type
99
99
  },
100
+ breadcrumbs: breadcrumbs.map(&:to_h),
100
101
  context: context,
101
102
  device: {
102
103
  hostname: hostname,
@@ -104,6 +105,7 @@ module Bugsnag
104
105
  },
105
106
  exceptions: exceptions,
106
107
  groupingHash: grouping_hash,
108
+ metaData: meta_data,
107
109
  session: session,
108
110
  severity: severity,
109
111
  severityReason: severity_reason,
@@ -111,19 +113,7 @@ module Bugsnag
111
113
  user: user
112
114
  }
113
115
 
114
- # cleanup character encodings
115
- payload_event = Bugsnag::Cleaner.clean_object_encoding(payload_event)
116
-
117
- # filter out sensitive values in (and cleanup encodings) metaData
118
- filter_cleaner = Bugsnag::Cleaner.new(configuration.meta_data_filters)
119
- payload_event[:metaData] = filter_cleaner.clean_object(meta_data)
120
- payload_event[:breadcrumbs] = breadcrumbs.map do |breadcrumb|
121
- breadcrumb_hash = breadcrumb.to_h
122
- breadcrumb_hash[:metaData] = filter_cleaner.clean_object(breadcrumb_hash[:metaData])
123
- breadcrumb_hash
124
- end
125
-
126
- payload_event.reject! {|k,v| v.nil? }
116
+ payload_event.reject! {|k, v| v.nil? }
127
117
 
128
118
  # return the payload hash
129
119
  {
@@ -9,27 +9,25 @@ module Bugsnag
9
9
 
10
10
  ##
11
11
  # Process a backtrace and the configuration into a parsed stacktrace.
12
+ #
13
+ # rubocop:todo Metrics/CyclomaticComplexity
12
14
  def initialize(backtrace, configuration)
13
15
  @configuration = configuration
14
16
 
15
17
  backtrace = caller if !backtrace || backtrace.empty?
16
18
 
17
19
  @processed_backtrace = backtrace.map do |trace|
20
+ # Parse the stacktrace line
18
21
  if trace.match(BACKTRACE_LINE_REGEX)
19
22
  file, line_str, method = [$1, $2, $3]
20
23
  elsif trace.match(JAVA_BACKTRACE_REGEX)
21
24
  method, file, line_str = [$1, $2, $3]
22
25
  end
23
26
 
24
- # Parse the stacktrace line
25
-
26
27
  next(nil) if file.nil?
27
28
 
28
29
  # Expand relative paths
29
- p = Pathname.new(file)
30
- if p.relative?
31
- file = p.realpath.to_s rescue file
32
- end
30
+ file = File.realpath(file) rescue file
33
31
 
34
32
  # Generate the stacktrace line hash
35
33
  trace_hash = {}
@@ -64,6 +62,7 @@ module Bugsnag
64
62
  end
65
63
  end.compact
66
64
  end
65
+ # rubocop:enable Metrics/CyclomaticComplexity
67
66
 
68
67
  ##
69
68
  # Returns the processed backtrace
@@ -90,4 +90,4 @@ RSpec.describe Bugsnag::Breadcrumbs::Breadcrumb do
90
90
  )
91
91
  end
92
92
  end
93
- end
93
+ end
@@ -31,31 +31,6 @@ RSpec.describe Bugsnag::Breadcrumbs::Validator do
31
31
  validator.validate(breadcrumb)
32
32
  end
33
33
 
34
- it "trims long messages to length and warns" do
35
- config = instance_double(Bugsnag::Configuration)
36
- allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types)
37
- validator = Bugsnag::Breadcrumbs::Validator.new(config)
38
-
39
- name = "1234567890123456789012345678901234567890"
40
-
41
- breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, {
42
- :auto => auto,
43
- :name => name,
44
- :type => type,
45
- :meta_data => meta_data,
46
- :meta_data= => nil
47
- })
48
-
49
- expect(breadcrumb).to_not receive(:ignore!)
50
- expect(breadcrumb).to receive(:name=).with("123456789012345678901234567890")
51
- expected_string = "Breadcrumb name trimmed to length 30. Original name: #{name}"
52
- expect(config).to receive(:debug).with(expected_string)
53
-
54
- validator.validate(breadcrumb)
55
- # Check the original message has not been modified
56
- expect(name).to eq("1234567890123456789012345678901234567890")
57
- end
58
-
59
34
  describe "tests meta_data types" do
60
35
  it "accepts Strings, Numerics, Booleans, & nil" do
61
36
  config = instance_double(Bugsnag::Configuration)
@@ -198,4 +173,4 @@ RSpec.describe Bugsnag::Breadcrumbs::Validator do
198
173
  end
199
174
  end
200
175
  end
201
- end
176
+ end
@@ -268,7 +268,7 @@ describe Bugsnag do
268
268
  )
269
269
  expect(breadcrumbs.to_a.size).to eq(1)
270
270
  expect(breadcrumbs.first.to_h).to match({
271
- :name => "123123123123123123123123123123",
271
+ :name => "123123123123123123123123123123456456456456456456456456456456",
272
272
  :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
273
273
  :metaData => {
274
274
  :a => 1
@@ -311,7 +311,7 @@ describe Bugsnag do
311
311
  Bugsnag.leave_breadcrumb("TestName")
312
312
  expect(breadcrumbs.to_a.size).to eq(1)
313
313
  expect(breadcrumbs.first.to_h).to match({
314
- :name => "123123123123123123123123123123",
314
+ :name => "123123123123123123123123123123456456456456456",
315
315
  :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
316
316
  :metaData => {
317
317
  :int => 1
@@ -3,7 +3,7 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Bugsnag::Cleaner do
6
- subject { described_class.new(nil) }
6
+ subject { Bugsnag::Cleaner.new(Bugsnag::Configuration.new) }
7
7
 
8
8
  describe "#clean_object" do
9
9
  is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
@@ -134,6 +134,17 @@ describe Bugsnag::Cleaner do
134
134
  expect(subject.clean_object(object)).to eq("[RECURSION]")
135
135
  end
136
136
 
137
+ it "cleans custom objects to show the id of the object if object responds to id method" do
138
+ class MacaronWithId
139
+ def id
140
+ 10
141
+ end
142
+ end
143
+
144
+ a = MacaronWithId.new
145
+ expect(subject.clean_object(a)).to eq("[OBJECT]: [Class]: #{a.class.name} [ID]: #{a.id}")
146
+ end
147
+
137
148
  it "cleans up binary strings properly" do
138
149
  if RUBY_VERSION > "1.9"
139
150
  obj = "Andr\xc7\xff"
@@ -156,23 +167,113 @@ describe Bugsnag::Cleaner do
156
167
  end
157
168
 
158
169
  it "filters by string inclusion" do
159
- expect(described_class.new(['f']).clean_object({ :foo => 'bar' })).to eq({ :foo => '[FILTERED]' })
160
- expect(described_class.new(['b']).clean_object({ :foo => 'bar' })).to eq({ :foo => 'bar' })
170
+ object = { events: { metaData: { foo: 'bar' } } }
171
+
172
+ configuration = Bugsnag::Configuration.new
173
+ configuration.meta_data_filters = ['f']
174
+
175
+ cleaner = Bugsnag::Cleaner.new(configuration)
176
+ expect(cleaner.clean_object(object)).to eq({ events: { metaData: { foo: '[FILTERED]' } } })
177
+
178
+ configuration = Bugsnag::Configuration.new
179
+ configuration.meta_data_filters = ['b']
180
+
181
+ cleaner = Bugsnag::Cleaner.new(configuration)
182
+ expect(cleaner.clean_object(object)).to eq({ events: { metaData: { foo: 'bar' } } })
161
183
  end
162
184
 
163
185
  it "filters by regular expression" do
164
- expect(described_class.new([/fb?/]).clean_object({ :foo => 'bar' })).to eq({ :foo => '[FILTERED]' })
165
- expect(described_class.new([/fb+/]).clean_object({ :foo => 'bar' })).to eq({ :foo => 'bar' })
186
+ object = { events: { metaData: { foo: 'bar' } } }
187
+
188
+ configuration = Bugsnag::Configuration.new
189
+ configuration.meta_data_filters = [/fb?/]
190
+
191
+ cleaner = Bugsnag::Cleaner.new(configuration)
192
+ expect(cleaner.clean_object(object)).to eq({ events: { metaData: { foo: '[FILTERED]' } } })
193
+
194
+ configuration = Bugsnag::Configuration.new
195
+ configuration.meta_data_filters = [/fb+/]
196
+
197
+ cleaner = Bugsnag::Cleaner.new(configuration)
198
+ expect(cleaner.clean_object(object)).to eq({ events: { metaData: { foo: 'bar' } } })
166
199
  end
167
200
 
168
201
  it "filters deeply nested keys" do
169
- params = {:foo => {:bar => "baz"}}
170
- expect(described_class.new([/^foo\.bar/]).clean_object(params)).to eq({:foo => {:bar => '[FILTERED]'}})
202
+ configuration = Bugsnag::Configuration.new
203
+ configuration.meta_data_filters = [/^foo\.bar/]
204
+
205
+ cleaner = Bugsnag::Cleaner.new(configuration)
206
+
207
+ params = { events: { metaData: { foo: { bar: 'baz' } } } }
208
+ expect(cleaner.clean_object(params)).to eq({ events: { metaData: { foo: { bar: '[FILTERED]' } } } })
171
209
  end
172
210
 
173
211
  it "filters deeply nested request parameters" do
174
- params = {:request => {:params => {:foo => {:bar => "baz"}}}}
175
- expect(described_class.new([/^foo\.bar/]).clean_object(params)).to eq({:request => {:params => {:foo => {:bar => '[FILTERED]'}}}})
212
+ configuration = Bugsnag::Configuration.new
213
+ configuration.meta_data_filters = [/^foo\.bar/]
214
+
215
+ cleaner = Bugsnag::Cleaner.new(configuration)
216
+
217
+ params = { events: { metaData: { request: { params: { foo: { bar: 'baz' } } } } } }
218
+ expect(cleaner.clean_object(params)).to eq({ events: { metaData: { request: { params: { foo: { bar: '[FILTERED]' } } } } } })
219
+ end
220
+
221
+ it "doesn't filter by string inclusion when the scope is not in 'scopes_to_filter'" do
222
+ object = { foo: 'bar' }
223
+
224
+ configuration = Bugsnag::Configuration.new
225
+ configuration.meta_data_filters = ['f']
226
+
227
+ cleaner = Bugsnag::Cleaner.new(configuration)
228
+
229
+ expect(cleaner.clean_object(object)).to eq({ foo: 'bar' })
230
+
231
+ configuration = Bugsnag::Configuration.new
232
+ configuration.meta_data_filters = ['b']
233
+
234
+ cleaner = Bugsnag::Cleaner.new(configuration)
235
+
236
+ expect(cleaner.clean_object(object)).to eq({ foo: 'bar' })
237
+ end
238
+
239
+ it "doesn't filter by regular expression when the scope is not in 'scopes_to_filter'" do
240
+ object = { foo: 'bar' }
241
+
242
+ configuration = Bugsnag::Configuration.new
243
+ configuration.meta_data_filters = [/fb?/]
244
+
245
+ cleaner = Bugsnag::Cleaner.new(configuration)
246
+
247
+ expect(cleaner.clean_object(object)).to eq({ foo: 'bar' })
248
+
249
+ configuration = Bugsnag::Configuration.new
250
+ configuration.meta_data_filters = [/fb+/]
251
+
252
+ cleaner = Bugsnag::Cleaner.new(configuration)
253
+
254
+ expect(cleaner.clean_object(object)).to eq({ foo: 'bar' })
255
+ end
256
+
257
+ it "doesn't filter deeply nested keys when the scope is not in 'scopes_to_filter'" do
258
+ params = { foo: { bar: 'baz' } }
259
+
260
+ configuration = Bugsnag::Configuration.new
261
+ configuration.meta_data_filters = [/^foo\.bar/]
262
+
263
+ cleaner = Bugsnag::Cleaner.new(configuration)
264
+
265
+ expect(cleaner.clean_object(params)).to eq({ foo: { bar: 'baz' } })
266
+ end
267
+
268
+ it "doesn't filter deeply nested request parameters when the scope is not in 'scopes_to_filter'" do
269
+ params = { request: { params: { foo: { bar: 'baz' } } } }
270
+
271
+ configuration = Bugsnag::Configuration.new
272
+ configuration.meta_data_filters = [/^foo\.bar/]
273
+
274
+ cleaner = Bugsnag::Cleaner.new(configuration)
275
+
276
+ expect(cleaner.clean_object(params)).to eq({ request: { params: { foo: { bar: 'baz' } } } })
176
277
  end
177
278
 
178
279
  it "filters objects which can't be stringified" do
@@ -187,7 +288,12 @@ describe Bugsnag::Cleaner do
187
288
 
188
289
  describe "#clean_url" do
189
290
  let(:filters) { [] }
190
- subject { described_class.new(filters).clean_url(url) }
291
+
292
+ subject do
293
+ configuration = Bugsnag::Configuration.new
294
+ configuration.meta_data_filters = filters
295
+ described_class.new(configuration).clean_url(url)
296
+ end
191
297
 
192
298
  context "with no filters configured" do
193
299
  let(:url) { "/dir/page?param1=value1&param2=value2" }