bugsnag 6.21.0 → 6.24.1
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 +102 -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 +240 -22
- 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 +9 -1
- data/lib/bugsnag/integrations/resque.rb +17 -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/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 +126 -4
- metadata +16 -6
@@ -44,9 +44,23 @@ module Bugsnag
|
|
44
44
|
:attributes => FRAMEWORK_ATTRIBUTES
|
45
45
|
}
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
metadata = payload
|
48
|
+
class_name = metadata['class']
|
49
|
+
|
50
|
+
# when using Active Job the payload "class" will always be the Resque
|
51
|
+
# "JobWrapper", so we need to unwrap the actual class name
|
52
|
+
if class_name == "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper"
|
53
|
+
unwrapped_class_name = metadata['args'][0]['job_class'] rescue nil
|
54
|
+
|
55
|
+
if unwrapped_class_name
|
56
|
+
class_name = unwrapped_class_name
|
57
|
+
metadata['wrapped'] ||= unwrapped_class_name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context = "#{class_name}@#{queue}"
|
62
|
+
report.meta_data.merge!({ context: context, payload: metadata })
|
63
|
+
report.automatic_context = context
|
50
64
|
end
|
51
65
|
end
|
52
66
|
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
|
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
|
@@ -34,16 +34,20 @@ module Bugsnag
|
|
34
34
|
# Starts a new session, storing it on the current thread.
|
35
35
|
#
|
36
36
|
# This allows Bugsnag to track error rates for a release.
|
37
|
+
#
|
38
|
+
# @return [void]
|
37
39
|
def start_session
|
38
|
-
return unless Bugsnag.configuration.enable_sessions
|
40
|
+
return unless Bugsnag.configuration.enable_sessions && Bugsnag.configuration.should_notify_release_stage?
|
41
|
+
|
39
42
|
start_delivery_thread
|
40
43
|
start_time = Time.now().utc().strftime('%Y-%m-%dT%H:%M:00')
|
41
44
|
new_session = {
|
42
|
-
:
|
43
|
-
:
|
44
|
-
|
45
|
-
|
46
|
-
:
|
45
|
+
id: SecureRandom.uuid,
|
46
|
+
startedAt: start_time,
|
47
|
+
paused?: false,
|
48
|
+
events: {
|
49
|
+
handled: 0,
|
50
|
+
unhandled: 0
|
47
51
|
}
|
48
52
|
}
|
49
53
|
SessionTracker.set_current_session(new_session)
|
@@ -52,6 +56,47 @@ module Bugsnag
|
|
52
56
|
|
53
57
|
alias_method :create_session, :start_session
|
54
58
|
|
59
|
+
##
|
60
|
+
# Stop any events being attributed to the current session until it is
|
61
|
+
# resumed or a new session is started
|
62
|
+
#
|
63
|
+
# @see resume_session
|
64
|
+
#
|
65
|
+
# @return [void]
|
66
|
+
def pause_session
|
67
|
+
current_session = SessionTracker.get_current_session
|
68
|
+
|
69
|
+
return unless current_session
|
70
|
+
|
71
|
+
current_session[:paused?] = true
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Resume the current session if it was previously paused. If there is no
|
76
|
+
# current session, a new session will be started
|
77
|
+
#
|
78
|
+
# @see pause_session
|
79
|
+
#
|
80
|
+
# @return [Boolean] true if a paused session was resumed
|
81
|
+
def resume_session
|
82
|
+
current_session = SessionTracker.get_current_session
|
83
|
+
|
84
|
+
if current_session
|
85
|
+
# if the session is paused then resume it, otherwise we don't need to
|
86
|
+
# do anything
|
87
|
+
if current_session[:paused?]
|
88
|
+
current_session[:paused?] = false
|
89
|
+
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
else
|
93
|
+
# if there's no current session, start a new one
|
94
|
+
start_session
|
95
|
+
end
|
96
|
+
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
55
100
|
##
|
56
101
|
# Delivers the current session_counts lists to the session endpoint.
|
57
102
|
def send_sessions
|
@@ -83,7 +128,7 @@ module Bugsnag
|
|
83
128
|
end
|
84
129
|
end
|
85
130
|
end
|
86
|
-
@delivery_thread = Concurrent::TimerTask.execute(execution_interval:
|
131
|
+
@delivery_thread = Concurrent::TimerTask.execute(execution_interval: 10) do
|
87
132
|
if @session_counts.size > 0
|
88
133
|
send_sessions
|
89
134
|
end
|
@@ -106,11 +151,6 @@ module Bugsnag
|
|
106
151
|
return
|
107
152
|
end
|
108
153
|
|
109
|
-
if !Bugsnag.configuration.should_notify_release_stage?
|
110
|
-
Bugsnag.configuration.debug("Not delivering sessions due to notify_release_stages :#{Bugsnag.configuration.notify_release_stages.inspect}")
|
111
|
-
return
|
112
|
-
end
|
113
|
-
|
114
154
|
body = {
|
115
155
|
:notifier => {
|
116
156
|
:name => Bugsnag::Report::NOTIFIER_NAME,
|
data/lib/bugsnag/stacktrace.rb
CHANGED
@@ -43,7 +43,7 @@ module Bugsnag
|
|
43
43
|
if defined?(configuration.project_root) && configuration.project_root.to_s != ''
|
44
44
|
trace_hash[:inProject] = true if file.start_with?(configuration.project_root.to_s)
|
45
45
|
file.sub!(/#{configuration.project_root}\//, "")
|
46
|
-
trace_hash.delete(:inProject) if
|
46
|
+
trace_hash.delete(:inProject) if vendor_path?(configuration, file)
|
47
47
|
end
|
48
48
|
|
49
49
|
# Strip common gem path prefixes
|
@@ -67,5 +67,14 @@ module Bugsnag
|
|
67
67
|
|
68
68
|
processed_backtrace
|
69
69
|
end
|
70
|
+
|
71
|
+
# @api private
|
72
|
+
def self.vendor_path?(configuration, file_path)
|
73
|
+
return true if configuration.vendor_path && file_path.match(configuration.vendor_path)
|
74
|
+
|
75
|
+
configuration.vendor_paths.any? do |vendor_path|
|
76
|
+
file_path.start_with?("#{vendor_path.sub(/\/$/, '')}/")
|
77
|
+
end
|
78
|
+
end
|
70
79
|
end
|
71
80
|
end
|