bugsnag 6.20.0 → 6.24.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/VERSION +1 -1
  4. data/lib/bugsnag/breadcrumb_type.rb +14 -0
  5. data/lib/bugsnag/breadcrumbs/breadcrumb.rb +34 -1
  6. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +1 -0
  7. data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +50 -0
  8. data/lib/bugsnag/cleaner.rb +31 -18
  9. data/lib/bugsnag/configuration.rb +243 -25
  10. data/lib/bugsnag/delivery/synchronous.rb +2 -2
  11. data/lib/bugsnag/delivery/thread_queue.rb +2 -2
  12. data/lib/bugsnag/endpoint_configuration.rb +11 -0
  13. data/lib/bugsnag/endpoint_validator.rb +80 -0
  14. data/lib/bugsnag/error.rb +25 -0
  15. data/lib/bugsnag/event.rb +7 -0
  16. data/lib/bugsnag/integrations/rack.rb +3 -3
  17. data/lib/bugsnag/integrations/rails/active_job.rb +102 -0
  18. data/lib/bugsnag/integrations/railtie.rb +43 -36
  19. data/lib/bugsnag/integrations/resque.rb +13 -3
  20. data/lib/bugsnag/middleware/active_job.rb +18 -0
  21. data/lib/bugsnag/middleware/delayed_job.rb +21 -2
  22. data/lib/bugsnag/middleware/exception_meta_data.rb +2 -0
  23. data/lib/bugsnag/middleware/rack_request.rb +84 -19
  24. data/lib/bugsnag/middleware/rails3_request.rb +2 -2
  25. data/lib/bugsnag/middleware/rake.rb +1 -1
  26. data/lib/bugsnag/middleware/session_data.rb +3 -1
  27. data/lib/bugsnag/middleware/sidekiq.rb +1 -1
  28. data/lib/bugsnag/middleware_stack.rb +6 -6
  29. data/lib/bugsnag/report.rb +166 -6
  30. data/lib/bugsnag/session_tracker.rb +52 -12
  31. data/lib/bugsnag/stacktrace.rb +10 -1
  32. data/lib/bugsnag/tasks/bugsnag.rake +1 -1
  33. data/lib/bugsnag/utility/duplicator.rb +124 -0
  34. data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
  35. data/lib/bugsnag.rb +139 -7
  36. metadata +12 -2
@@ -9,11 +9,44 @@ require "bugsnag/integrations/rails/rails_breadcrumbs"
9
9
 
10
10
  module Bugsnag
11
11
  class Railtie < ::Rails::Railtie
12
-
13
12
  FRAMEWORK_ATTRIBUTES = {
14
13
  :framework => "Rails"
15
14
  }
16
15
 
16
+ ##
17
+ # Subscribes to an ActiveSupport event, leaving a breadcrumb when it triggers
18
+ #
19
+ # @api private
20
+ # @param event [Hash] details of the event to subscribe to
21
+ def event_subscription(event)
22
+ ActiveSupport::Notifications.subscribe(event[:id]) do |*, event_id, data|
23
+ filtered_data = data.slice(*event[:allowed_data])
24
+ filtered_data[:event_name] = event[:id]
25
+ filtered_data[:event_id] = event_id
26
+
27
+ if event[:id] == "sql.active_record"
28
+ if data.key?(:binds)
29
+ binds = data[:binds].each_with_object({}) { |bind, output| output[bind.name] = '?' if defined?(bind.name) }
30
+ filtered_data[:binds] = JSON.dump(binds) unless binds.empty?
31
+ end
32
+
33
+ # Rails < 6.1 included connection_id in the event data, but now
34
+ # includes the connection object instead
35
+ if data.key?(:connection) && !data.key?(:connection_id)
36
+ # the connection ID is the object_id of the connection object
37
+ filtered_data[:connection_id] = data[:connection].object_id
38
+ end
39
+ end
40
+
41
+ Bugsnag.leave_breadcrumb(
42
+ event[:message],
43
+ filtered_data,
44
+ event[:type],
45
+ :auto
46
+ )
47
+ end
48
+ end
49
+
17
50
  rake_tasks do
18
51
  require "bugsnag/integrations/rake"
19
52
  load "bugsnag/tasks/bugsnag.rake"
@@ -27,7 +60,7 @@ module Bugsnag
27
60
  config.logger = ::Rails.logger
28
61
  config.release_stage ||= ::Rails.env.to_s
29
62
  config.project_root = ::Rails.root.to_s
30
- config.middleware.insert_before Bugsnag::Middleware::Callbacks, Bugsnag::Middleware::Rails3Request
63
+ config.internal_middleware.use(Bugsnag::Middleware::Rails3Request)
31
64
  config.runtime_versions["rails"] = ::Rails::VERSION::STRING
32
65
  end
33
66
 
@@ -41,6 +74,14 @@ module Bugsnag
41
74
  include Bugsnag::Rails::ActiveRecordRescue
42
75
  end
43
76
 
77
+ ActiveSupport.on_load(:active_job) do
78
+ require "bugsnag/middleware/active_job"
79
+ Bugsnag.configuration.internal_middleware.use(Bugsnag::Middleware::ActiveJob)
80
+
81
+ require "bugsnag/integrations/rails/active_job"
82
+ include Bugsnag::Rails::ActiveJob
83
+ end
84
+
44
85
  Bugsnag::Rails::DEFAULT_RAILS_BREADCRUMBS.each { |event| event_subscription(event) }
45
86
 
46
87
  # Make sure we don't overwrite the value set by another integration because
@@ -80,39 +121,5 @@ module Bugsnag
80
121
  Bugsnag.configuration.warn("Unable to add Bugsnag::Rack middleware as the middleware stack is frozen")
81
122
  end
82
123
  end
83
-
84
- ##
85
- # Subscribes to an ActiveSupport event, leaving a breadcrumb when it triggers
86
- #
87
- # @api private
88
- # @param event [Hash] details of the event to subscribe to
89
- def event_subscription(event)
90
- ActiveSupport::Notifications.subscribe(event[:id]) do |*, event_id, data|
91
- filtered_data = data.slice(*event[:allowed_data])
92
- filtered_data[:event_name] = event[:id]
93
- filtered_data[:event_id] = event_id
94
-
95
- if event[:id] == "sql.active_record"
96
- if data.key?(:binds)
97
- binds = data[:binds].each_with_object({}) { |bind, output| output[bind.name] = '?' if defined?(bind.name) }
98
- filtered_data[:binds] = JSON.dump(binds) unless binds.empty?
99
- end
100
-
101
- # Rails < 6.1 included connection_id in the event data, but now
102
- # includes the connection object instead
103
- if data.key?(:connection) && !data.key?(:connection_id)
104
- # the connection ID is the object_id of the connection object
105
- filtered_data[:connection_id] = data[:connection].object_id
106
- end
107
- end
108
-
109
- Bugsnag.leave_breadcrumb(
110
- event[:message],
111
- filtered_data,
112
- event[:type],
113
- :auto
114
- )
115
- end
116
- end
117
124
  end
118
125
  end
@@ -44,9 +44,19 @@ module Bugsnag
44
44
  :attributes => FRAMEWORK_ATTRIBUTES
45
45
  }
46
46
 
47
- context = "#{payload['class']}@#{queue}"
48
- report.meta_data.merge!({:context => context, :payload => payload})
49
- report.context = context
47
+ metadata = payload
48
+ class_name = payload['class']
49
+
50
+ # when using Active Job the payload "class" will always be the Resque
51
+ # "JobWrapper", not the actual job class so we need to fix this here
52
+ if metadata['args'] && metadata['args'][0] && metadata['args'][0]['job_class']
53
+ class_name = metadata['args'][0]['job_class']
54
+ metadata['wrapped'] ||= class_name
55
+ end
56
+
57
+ context = "#{class_name}@#{queue}"
58
+ report.meta_data.merge!({ context: context, payload: metadata })
59
+ report.automatic_context = context
50
60
  end
51
61
  end
52
62
  end
@@ -0,0 +1,18 @@
1
+ module Bugsnag::Middleware
2
+ class ActiveJob
3
+ def initialize(bugsnag)
4
+ @bugsnag = bugsnag
5
+ end
6
+
7
+ def call(report)
8
+ data = report.request_data[:active_job]
9
+
10
+ if data
11
+ report.add_tab(:active_job, data)
12
+ report.automatic_context = "#{data[:job_name]}@#{data[:queue]}"
13
+ end
14
+
15
+ @bugsnag.call(report)
16
+ end
17
+ end
18
+ end
@@ -2,6 +2,11 @@ module Bugsnag::Middleware
2
2
  ##
3
3
  # Attaches delayed_job information to an error report
4
4
  class DelayedJob
5
+ # Active Job's queue adapter sets the "display_name" to this format. This
6
+ # breaks the event context as the ID and arguments are included, which will
7
+ # differ between executions of the same job
8
+ ACTIVE_JOB_DISPLAY_NAME = /^.* \[[0-9a-f-]+\] from DelayedJob\(.*\) with arguments: \[.*\]$/
9
+
5
10
  def initialize(bugsnag)
6
11
  @bugsnag = bugsnag
7
12
  end
@@ -23,8 +28,10 @@ module Bugsnag::Middleware
23
28
  if job.respond_to?(:payload_object)
24
29
  job_data[:active_job] = job.payload_object.job_data if job.payload_object.respond_to?(:job_data)
25
30
  payload_data = construct_job_payload(job.payload_object)
26
- report.context = payload_data[:display_name] if payload_data.include?(:display_name)
27
- report.context ||= payload_data[:class] if payload_data.include?(:class)
31
+
32
+ context = get_context(payload_data, job_data[:active_job])
33
+ report.automatic_context = context unless context.nil?
34
+
28
35
  job_data[:payload] = payload_data
29
36
  end
30
37
 
@@ -70,5 +77,17 @@ module Bugsnag::Middleware
70
77
  end
71
78
  data
72
79
  end
80
+
81
+ private
82
+
83
+ def get_context(payload_data, active_job_data)
84
+ if payload_data.include?(:display_name) && !ACTIVE_JOB_DISPLAY_NAME.match?(payload_data[:display_name])
85
+ payload_data[:display_name]
86
+ elsif active_job_data && active_job_data['job_class'] && active_job_data['queue_name']
87
+ "#{active_job_data['job_class']}@#{active_job_data['queue_name']}"
88
+ elsif payload_data.include?(:class)
89
+ payload_data[:class]
90
+ end
91
+ end
73
92
  end
74
93
  end
@@ -16,6 +16,8 @@ module Bugsnag::Middleware
16
16
 
17
17
  if exception.respond_to?(:bugsnag_context)
18
18
  context = exception.bugsnag_context
19
+ # note: this should set 'context' not 'automatic_context' as it's a
20
+ # user-supplied value
19
21
  report.context = context if context.is_a?(String)
20
22
  end
21
23
 
@@ -1,8 +1,11 @@
1
+ require "json"
2
+
1
3
  module Bugsnag::Middleware
2
4
  ##
3
5
  # Extracts and attaches rack data to an error report
4
6
  class RackRequest
5
7
  SPOOF = "[SPOOF]".freeze
8
+ COOKIE_HEADER = "Cookie".freeze
6
9
 
7
10
  def initialize(bugsnag)
8
11
  @bugsnag = bugsnag
@@ -18,8 +21,8 @@ module Bugsnag::Middleware
18
21
  client_ip = request.ip.to_s rescue SPOOF
19
22
  session = env["rack.session"]
20
23
 
21
- # Set the context
22
- report.context = "#{request.request_method} #{request.path}"
24
+ # Set the automatic context
25
+ report.automatic_context = "#{request.request_method} #{request.path}"
23
26
 
24
27
  # Set a sensible default for user_id
25
28
  report.user["id"] = request.ip
@@ -42,22 +45,6 @@ module Bugsnag::Middleware
42
45
  Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.referer: #{stde}"
43
46
  end
44
47
 
45
- headers = {}
46
-
47
- env.each_pair do |key, value|
48
- if key.to_s.start_with?("HTTP_")
49
- header_key = key[5..-1]
50
- elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
51
- header_key = key
52
- else
53
- next
54
- end
55
-
56
- headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
57
- end
58
-
59
- headers["Referer"] = referer if headers["Referer"]
60
-
61
48
  # Add a request tab
62
49
  report.add_tab(:request, {
63
50
  :url => url,
@@ -65,9 +52,17 @@ module Bugsnag::Middleware
65
52
  :params => params.to_hash,
66
53
  :referer => referer,
67
54
  :clientIp => client_ip,
68
- :headers => headers
55
+ :headers => format_headers(env, referer)
69
56
  })
70
57
 
58
+ # add the HTTP version if present
59
+ if env["SERVER_PROTOCOL"]
60
+ report.add_metadata(:request, :httpVersion, env["SERVER_PROTOCOL"])
61
+ end
62
+
63
+ add_request_body(report, request, env)
64
+ add_cookies(report, request)
65
+
71
66
  # Add an environment tab
72
67
  if report.configuration.send_environment
73
68
  report.add_tab(:environment, env)
@@ -87,5 +82,75 @@ module Bugsnag::Middleware
87
82
 
88
83
  @bugsnag.call(report)
89
84
  end
85
+
86
+ private
87
+
88
+ def format_headers(env, referer)
89
+ headers = {}
90
+
91
+ env.each_pair do |key, value|
92
+ if key.to_s.start_with?("HTTP_")
93
+ header_key = key[5..-1]
94
+ elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
95
+ header_key = key
96
+ else
97
+ next
98
+ end
99
+
100
+ headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
101
+ end
102
+
103
+ headers["Referer"] = referer if headers["Referer"]
104
+
105
+ headers
106
+ end
107
+
108
+ def add_request_body(report, request, env)
109
+ body = parsed_request_body(request, env)
110
+
111
+ # this request may not have a body
112
+ return unless body.is_a?(Hash) && !body.empty?
113
+
114
+ report.add_metadata(:request, :body, body)
115
+ end
116
+
117
+ def parsed_request_body(request, env)
118
+ return request.POST rescue nil if request.form_data?
119
+
120
+ content_type = env["CONTENT_TYPE"]
121
+
122
+ return nil if content_type.nil?
123
+
124
+ if content_type.include?('/json') || content_type.include?('+json')
125
+ begin
126
+ body = request.body
127
+
128
+ return JSON.parse(body.read)
129
+ rescue StandardError
130
+ return nil
131
+ ensure
132
+ # the body must be rewound so other things can read it after we do
133
+ body.rewind
134
+ end
135
+ end
136
+
137
+ nil
138
+ end
139
+
140
+ def add_cookies(report, request)
141
+ return unless record_cookies?
142
+
143
+ cookies = request.cookies rescue nil
144
+
145
+ return unless cookies.is_a?(Hash) && !cookies.empty?
146
+
147
+ report.add_metadata(:request, :cookies, cookies)
148
+ end
149
+
150
+ def record_cookies?
151
+ # only record cookies in the request if none of the filters match "Cookie"
152
+ # the "Cookie" header will be filtered as normal
153
+ !Bugsnag.cleaner.filters_match?(COOKIE_HEADER)
154
+ end
90
155
  end
91
156
  end
@@ -15,8 +15,8 @@ module Bugsnag::Middleware
15
15
  client_ip = env["action_dispatch.remote_ip"].to_s rescue SPOOF
16
16
 
17
17
  if params
18
- # Set the context
19
- report.context = "#{params[:controller]}##{params[:action]}"
18
+ # Set the automatic context
19
+ report.automatic_context = "#{params[:controller]}##{params[:action]}"
20
20
 
21
21
  # Augment the request tab
22
22
  report.add_tab(:request, {
@@ -16,7 +16,7 @@ module Bugsnag::Middleware
16
16
  :arguments => task.arg_description
17
17
  })
18
18
 
19
- report.context ||= task.name
19
+ report.automatic_context ||= task.name
20
20
  end
21
21
 
22
22
  @bugsnag.call(report)
@@ -8,12 +8,14 @@ module Bugsnag::Middleware
8
8
 
9
9
  def call(report)
10
10
  session = Bugsnag::SessionTracker.get_current_session
11
- unless session.nil?
11
+
12
+ if session && !session[:paused?]
12
13
  if report.unhandled
13
14
  session[:events][:unhandled] += 1
14
15
  else
15
16
  session[:events][:handled] += 1
16
17
  end
18
+
17
19
  report.session = session
18
20
  end
19
21
 
@@ -10,7 +10,7 @@ module Bugsnag::Middleware
10
10
  sidekiq = report.request_data[:sidekiq]
11
11
  if sidekiq
12
12
  report.add_tab(:sidekiq, sidekiq)
13
- report.context ||= "#{sidekiq[:msg]['wrapped'] || sidekiq[:msg]['class']}@#{sidekiq[:msg]['queue']}"
13
+ report.automatic_context ||= "#{sidekiq[:msg]['wrapped'] || sidekiq[:msg]['class']}@#{sidekiq[:msg]['queue']}"
14
14
  end
15
15
  @bugsnag.call(report)
16
16
  end
@@ -131,8 +131,8 @@ module Bugsnag
131
131
  #
132
132
  # @return [Array<Proc>]
133
133
  def middleware_procs
134
- # Split the middleware into separate lists of Procs and Classes
135
- procs, classes = @middlewares.partition {|middleware| middleware.is_a?(Proc) }
134
+ # Split the middleware into separate lists of callables (e.g. Proc, Lambda, Method) and Classes
135
+ callables, classes = @middlewares.partition {|middleware| middleware.respond_to?(:call) }
136
136
 
137
137
  # Wrap the classes in a proc that, when called, news up the middleware and
138
138
  # passes the next middleware in the queue
@@ -140,12 +140,12 @@ module Bugsnag
140
140
  proc {|next_middleware| middleware.new(next_middleware) }
141
141
  end
142
142
 
143
- # Wrap the list of procs in a proc that, when called, wraps them in an
143
+ # Wrap the list of callables in a proc that, when called, wraps them in an
144
144
  # 'OnErrorCallbacks' instance that also has a reference to the next middleware
145
- wrapped_procs = proc {|next_middleware| OnErrorCallbacks.new(next_middleware, procs) }
145
+ wrapped_callables = proc {|next_middleware| OnErrorCallbacks.new(next_middleware, callables) }
146
146
 
147
- # Return the combined middleware and wrapped procs
148
- middleware_instances.push(wrapped_procs)
147
+ # Return the combined middleware and wrapped callables
148
+ middleware_instances.push(wrapped_callables)
149
149
  end
150
150
  end
151
151
  end
@@ -1,8 +1,10 @@
1
1
  require "json"
2
2
  require "pathname"
3
+ require "bugsnag/error"
3
4
  require "bugsnag/stacktrace"
4
5
 
5
6
  module Bugsnag
7
+ # rubocop:todo Metrics/ClassLength
6
8
  class Report
7
9
  NOTIFIER_NAME = "Ruby Bugsnag Notifier"
8
10
  NOTIFIER_VERSION = Bugsnag::VERSION
@@ -45,16 +47,13 @@ module Bugsnag
45
47
  # @return [Configuration]
46
48
  attr_accessor :configuration
47
49
 
48
- # Additional context for this report
49
- # @return [String, nil]
50
- attr_accessor :context
51
-
52
50
  # The delivery method that will be used for this report
53
51
  # @see Configuration#delivery_method
54
52
  # @return [Symbol]
55
53
  attr_accessor :delivery_method
56
54
 
57
55
  # The list of exceptions in this report
56
+ # @deprecated Use {#errors} instead
58
57
  # @return [Array<Hash>]
59
58
  attr_accessor :exceptions
60
59
 
@@ -72,10 +71,12 @@ module Bugsnag
72
71
  attr_accessor :grouping_hash
73
72
 
74
73
  # Arbitrary metadata attached to this report
74
+ # @deprecated Use {#metadata} instead
75
75
  # @return [Hash]
76
76
  attr_accessor :meta_data
77
77
 
78
78
  # The raw Exception instances for this report
79
+ # @deprecated Use {#original_error} instead
79
80
  # @see #exceptions
80
81
  # @return [Array<Exception>]
81
82
  attr_accessor :raw_exceptions
@@ -102,31 +103,67 @@ module Bugsnag
102
103
  # @return [Hash]
103
104
  attr_accessor :user
104
105
 
106
+ # A list of errors in this report
107
+ # @return [Array<Error>]
108
+ attr_reader :errors
109
+
110
+ # The Exception instance this report was created for
111
+ # @return [Exception]
112
+ attr_reader :original_error
113
+
105
114
  ##
106
115
  # Initializes a new report from an exception.
107
116
  def initialize(exception, passed_configuration, auto_notify=false)
117
+ # store the creation time for use as device.time
118
+ @created_at = Time.now.utc.iso8601(3)
119
+
108
120
  @should_ignore = false
109
121
  @unhandled = auto_notify
122
+ @initial_unhandled = @unhandled
110
123
 
111
124
  self.configuration = passed_configuration
112
125
 
126
+ @original_error = exception
113
127
  self.raw_exceptions = generate_raw_exceptions(exception)
114
128
  self.exceptions = generate_exception_list
129
+ @errors = generate_error_list
115
130
 
116
131
  self.api_key = configuration.api_key
117
132
  self.app_type = configuration.app_type
118
133
  self.app_version = configuration.app_version
119
134
  self.breadcrumbs = []
135
+ self.context = configuration.context if configuration.context_set?
120
136
  self.delivery_method = configuration.delivery_method
121
137
  self.hostname = configuration.hostname
122
138
  self.runtime_versions = configuration.runtime_versions.dup
123
- self.meta_data = {}
139
+ self.meta_data = Utility::Duplicator.duplicate(configuration.metadata)
124
140
  self.release_stage = configuration.release_stage
125
141
  self.severity = auto_notify ? "error" : "warning"
126
142
  self.severity_reason = auto_notify ? {:type => UNHANDLED_EXCEPTION} : {:type => HANDLED_EXCEPTION}
127
143
  self.user = {}
144
+
145
+ @metadata_delegate = Utility::MetadataDelegate.new
146
+ end
147
+
148
+ ##
149
+ # Additional context for this report
150
+ # @!attribute context
151
+ # @return [String, nil]
152
+ def context
153
+ return @context if defined?(@context)
154
+
155
+ @automatic_context
128
156
  end
129
157
 
158
+ attr_writer :context
159
+
160
+ ##
161
+ # Context set automatically by Bugsnag uses this attribute, which prevents
162
+ # it from overwriting the user-supplied context
163
+ # @api private
164
+ # @return [String, nil]
165
+ attr_accessor :automatic_context
166
+
130
167
  ##
131
168
  # Add a new metadata tab to this notification.
132
169
  #
@@ -135,6 +172,8 @@ module Bugsnag
135
172
  # exists, this will be merged with the existing values. If a Hash is not
136
173
  # given, the value will be placed into the 'custom' tab
137
174
  # @return [void]
175
+ #
176
+ # @deprecated Use {#add_metadata} instead
138
177
  def add_tab(name, value)
139
178
  return if name.nil?
140
179
 
@@ -153,6 +192,8 @@ module Bugsnag
153
192
  #
154
193
  # @param name [String]
155
194
  # @return [void]
195
+ #
196
+ # @deprecated Use {#clear_metadata} instead
156
197
  def remove_tab(name)
157
198
  return if name.nil?
158
199
 
@@ -175,7 +216,8 @@ module Bugsnag
175
216
  context: context,
176
217
  device: {
177
218
  hostname: hostname,
178
- runtimeVersions: runtime_versions
219
+ runtimeVersions: runtime_versions,
220
+ time: @created_at
179
221
  },
180
222
  exceptions: exceptions,
181
223
  groupingHash: grouping_hash,
@@ -257,8 +299,119 @@ module Bugsnag
257
299
  end
258
300
  end
259
301
 
302
+ # A Hash containing arbitrary metadata
303
+ # @!attribute metadata
304
+ # @return [Hash]
305
+ def metadata
306
+ @meta_data
307
+ end
308
+
309
+ # @param metadata [Hash]
310
+ # @return [void]
311
+ def metadata=(metadata)
312
+ @meta_data = metadata
313
+ end
314
+
315
+ ##
316
+ # Data from the current HTTP request. May be nil if no data has been recorded
317
+ #
318
+ # @return [Hash, nil]
319
+ def request
320
+ @meta_data[:request]
321
+ end
322
+
323
+ ##
324
+ # Add values to metadata
325
+ #
326
+ # @overload add_metadata(section, data)
327
+ # Merges data into the given section of metadata
328
+ # @param section [String, Symbol]
329
+ # @param data [Hash]
330
+ #
331
+ # @overload add_metadata(section, key, value)
332
+ # Sets key to value in the given section of metadata. If the value is nil
333
+ # the key will be deleted
334
+ # @param section [String, Symbol]
335
+ # @param key [String, Symbol]
336
+ # @param value
337
+ #
338
+ # @return [void]
339
+ def add_metadata(section, key_or_data, *args)
340
+ @metadata_delegate.add_metadata(@meta_data, section, key_or_data, *args)
341
+ end
342
+
343
+ ##
344
+ # Clear values from metadata
345
+ #
346
+ # @overload clear_metadata(section)
347
+ # Clears the given section of metadata
348
+ # @param section [String, Symbol]
349
+ #
350
+ # @overload clear_metadata(section, key)
351
+ # Clears the key in the given section of metadata
352
+ # @param section [String, Symbol]
353
+ # @param key [String, Symbol]
354
+ #
355
+ # @return [void]
356
+ def clear_metadata(section, *args)
357
+ @metadata_delegate.clear_metadata(@meta_data, section, *args)
358
+ end
359
+
360
+ ##
361
+ # Set information about the current user
362
+ #
363
+ # Additional user fields can be added as metadata in a "user" section
364
+ #
365
+ # Setting a field to 'nil' will remove it from the user data
366
+ #
367
+ # @param id [String, nil]
368
+ # @param email [String, nil]
369
+ # @param name [String, nil]
370
+ # @return [void]
371
+ def set_user(id = nil, email = nil, name = nil)
372
+ new_user = { id: id, email: email, name: name }
373
+ new_user.reject! { |key, value| value.nil? }
374
+
375
+ @user = new_user
376
+ end
377
+
378
+ def unhandled=(new_unhandled)
379
+ # fix the handled/unhandled counts in the current session
380
+ update_handled_counts(new_unhandled, @unhandled)
381
+
382
+ @unhandled = new_unhandled
383
+ end
384
+
385
+ ##
386
+ # Returns true if the unhandled flag has been changed from its initial value
387
+ #
388
+ # @api private
389
+ # @return [Boolean]
390
+ def unhandled_overridden?
391
+ @unhandled != @initial_unhandled
392
+ end
393
+
260
394
  private
261
395
 
396
+ def update_handled_counts(is_unhandled, was_unhandled)
397
+ # do nothing if there is no session to update
398
+ return if @session.nil?
399
+
400
+ # increment the counts for the current unhandled value
401
+ if is_unhandled
402
+ @session[:events][:unhandled] += 1
403
+ else
404
+ @session[:events][:handled] += 1
405
+ end
406
+
407
+ # decrement the counts for the previous unhandled value
408
+ if was_unhandled
409
+ @session[:events][:unhandled] -= 1
410
+ else
411
+ @session[:events][:handled] -= 1
412
+ end
413
+ end
414
+
262
415
  def generate_exception_list
263
416
  raw_exceptions.map do |exception|
264
417
  {
@@ -269,6 +422,12 @@ module Bugsnag
269
422
  end
270
423
  end
271
424
 
425
+ def generate_error_list
426
+ exceptions.map do |exception|
427
+ Error.new(exception[:errorClass], exception[:message], exception[:stacktrace])
428
+ end
429
+ end
430
+
272
431
  def error_class(exception)
273
432
  # The "Class" check is for some strange exceptions like Timeout::Error
274
433
  # which throw the error class instead of an instance
@@ -311,4 +470,5 @@ module Bugsnag
311
470
  exceptions
312
471
  end
313
472
  end
473
+ # rubocop:enable Metrics/ClassLength
314
474
  end