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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +110 -0
- data/VERSION +1 -1
- data/lib/bugsnag/breadcrumb_type.rb +14 -0
- data/lib/bugsnag/breadcrumbs/breadcrumb.rb +34 -1
- data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +1 -0
- data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +50 -0
- data/lib/bugsnag/cleaner.rb +31 -18
- data/lib/bugsnag/configuration.rb +243 -25
- data/lib/bugsnag/delivery/synchronous.rb +2 -2
- data/lib/bugsnag/delivery/thread_queue.rb +2 -2
- data/lib/bugsnag/endpoint_configuration.rb +11 -0
- data/lib/bugsnag/endpoint_validator.rb +80 -0
- data/lib/bugsnag/error.rb +25 -0
- data/lib/bugsnag/event.rb +7 -0
- data/lib/bugsnag/integrations/rack.rb +3 -3
- data/lib/bugsnag/integrations/rails/active_job.rb +102 -0
- data/lib/bugsnag/integrations/railtie.rb +43 -36
- data/lib/bugsnag/integrations/resque.rb +13 -3
- data/lib/bugsnag/middleware/active_job.rb +18 -0
- data/lib/bugsnag/middleware/delayed_job.rb +21 -2
- data/lib/bugsnag/middleware/exception_meta_data.rb +2 -0
- data/lib/bugsnag/middleware/rack_request.rb +84 -19
- data/lib/bugsnag/middleware/rails3_request.rb +2 -2
- data/lib/bugsnag/middleware/rake.rb +1 -1
- data/lib/bugsnag/middleware/session_data.rb +3 -1
- data/lib/bugsnag/middleware/sidekiq.rb +1 -1
- data/lib/bugsnag/middleware_stack.rb +6 -6
- data/lib/bugsnag/report.rb +166 -6
- data/lib/bugsnag/session_tracker.rb +52 -12
- data/lib/bugsnag/stacktrace.rb +10 -1
- data/lib/bugsnag/tasks/bugsnag.rake +1 -1
- data/lib/bugsnag/utility/duplicator.rb +124 -0
- data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
- data/lib/bugsnag.rb +139 -7
- 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.
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
27
|
-
|
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.
|
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 =>
|
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.
|
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, {
|
@@ -8,12 +8,14 @@ module Bugsnag::Middleware
|
|
8
8
|
|
9
9
|
def call(report)
|
10
10
|
session = Bugsnag::SessionTracker.get_current_session
|
11
|
-
|
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.
|
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
|
135
|
-
|
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
|
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
|
-
|
145
|
+
wrapped_callables = proc {|next_middleware| OnErrorCallbacks.new(next_middleware, callables) }
|
146
146
|
|
147
|
-
# Return the combined middleware and wrapped
|
148
|
-
middleware_instances.push(
|
147
|
+
# Return the combined middleware and wrapped callables
|
148
|
+
middleware_instances.push(wrapped_callables)
|
149
149
|
end
|
150
150
|
end
|
151
151
|
end
|
data/lib/bugsnag/report.rb
CHANGED
@@ -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
|