bugsnag 6.13.1 → 6.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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" }