bugsnag 4.2.1 → 6.27.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 +5 -5
- data/.yardopts +12 -0
- data/CHANGELOG.md +814 -0
- data/README.md +21 -25
- data/VERSION +1 -1
- data/bugsnag.gemspec +19 -8
- data/lib/bugsnag/breadcrumb_type.rb +14 -0
- data/lib/bugsnag/breadcrumbs/breadcrumb.rb +109 -0
- data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +13 -0
- data/lib/bugsnag/breadcrumbs/on_breadcrumb_callback_list.rb +48 -0
- data/lib/bugsnag/breadcrumbs/validator.rb +29 -0
- data/lib/bugsnag/cleaner.rb +170 -59
- data/lib/bugsnag/code_extractor.rb +137 -0
- data/lib/bugsnag/configuration.rb +670 -45
- data/lib/bugsnag/delivery/synchronous.rb +31 -14
- data/lib/bugsnag/delivery/thread_queue.rb +23 -6
- data/lib/bugsnag/delivery.rb +13 -0
- 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 +5 -0
- data/lib/bugsnag/feature_flag.rb +74 -0
- data/lib/bugsnag/helpers.rb +121 -25
- data/lib/bugsnag/integrations/delayed_job.rb +51 -0
- data/lib/bugsnag/integrations/mailman.rb +43 -0
- data/lib/bugsnag/integrations/mongo.rb +133 -0
- data/lib/bugsnag/integrations/que.rb +53 -0
- data/lib/bugsnag/integrations/rack.rb +83 -0
- data/lib/bugsnag/integrations/rails/active_job.rb +100 -0
- data/lib/bugsnag/{rails → integrations/rails}/active_record_rescue.rb +10 -1
- data/lib/bugsnag/{rails → integrations/rails}/controller_methods.rb +1 -9
- data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +115 -0
- data/lib/bugsnag/integrations/railtie.rb +153 -0
- data/lib/bugsnag/integrations/rake.rb +74 -0
- data/lib/bugsnag/integrations/resque.rb +94 -0
- data/lib/bugsnag/integrations/shoryuken.rb +50 -0
- data/lib/bugsnag/integrations/sidekiq.rb +68 -0
- data/lib/bugsnag/meta_data.rb +1 -0
- data/lib/bugsnag/middleware/active_job.rb +18 -0
- data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
- data/lib/bugsnag/middleware/callbacks.rb +6 -8
- data/lib/bugsnag/middleware/classify_error.rb +50 -0
- data/lib/bugsnag/middleware/clearance_user.rb +33 -0
- data/lib/bugsnag/middleware/delayed_job.rb +93 -0
- data/lib/bugsnag/middleware/discard_error_class.rb +30 -0
- data/lib/bugsnag/middleware/exception_meta_data.rb +42 -0
- data/lib/bugsnag/middleware/ignore_error_class.rb +26 -0
- data/lib/bugsnag/middleware/mailman.rb +6 -4
- data/lib/bugsnag/middleware/rack_request.rb +126 -30
- data/lib/bugsnag/middleware/rails3_request.rb +15 -17
- data/lib/bugsnag/middleware/rake.rb +7 -5
- data/lib/bugsnag/middleware/session_data.rb +25 -0
- data/lib/bugsnag/middleware/sidekiq.rb +9 -4
- data/lib/bugsnag/middleware/suggestion_data.rb +34 -0
- data/lib/bugsnag/middleware/warden_user.rb +11 -6
- data/lib/bugsnag/middleware_stack.rb +62 -9
- data/lib/bugsnag/on_error_callbacks.rb +33 -0
- data/lib/bugsnag/report.rb +516 -0
- data/lib/bugsnag/session_tracker.rb +182 -0
- data/lib/bugsnag/stacktrace.rb +82 -0
- data/lib/bugsnag/tasks/bugsnag.rake +2 -70
- data/lib/bugsnag/utility/circular_buffer.rb +62 -0
- data/lib/bugsnag/utility/duplicator.rb +124 -0
- data/lib/bugsnag/utility/feature_data_store.rb +41 -0
- data/lib/bugsnag/utility/feature_flag_delegate.rb +89 -0
- data/lib/bugsnag/utility/metadata_delegate.rb +102 -0
- data/lib/bugsnag.rb +528 -80
- metadata +61 -123
- data/.document +0 -5
- data/.gitignore +0 -52
- data/.rspec +0 -3
- data/.travis.yml +0 -14
- data/CONTRIBUTING.md +0 -47
- data/Gemfile +0 -2
- data/Rakefile +0 -29
- data/lib/bugsnag/capistrano.rb +0 -7
- data/lib/bugsnag/capistrano2.rb +0 -32
- data/lib/bugsnag/delay/resque.rb +0 -21
- data/lib/bugsnag/delayed_job.rb +0 -57
- data/lib/bugsnag/deploy.rb +0 -34
- data/lib/bugsnag/mailman.rb +0 -28
- data/lib/bugsnag/middleware/rails2_request.rb +0 -52
- data/lib/bugsnag/notification.rb +0 -459
- data/lib/bugsnag/rack.rb +0 -53
- data/lib/bugsnag/rails/action_controller_rescue.rb +0 -62
- data/lib/bugsnag/rails.rb +0 -66
- data/lib/bugsnag/railtie.rb +0 -80
- data/lib/bugsnag/rake.rb +0 -25
- data/lib/bugsnag/resque.rb +0 -40
- data/lib/bugsnag/sidekiq.rb +0 -42
- data/lib/bugsnag/tasks/bugsnag.cap +0 -48
- data/rails/init.rb +0 -7
- data/spec/cleaner_spec.rb +0 -138
- data/spec/code_spec.rb +0 -86
- data/spec/fixtures/crashes/end_of_file.rb +0 -9
- data/spec/fixtures/crashes/short_file.rb +0 -1
- data/spec/fixtures/crashes/start_of_file.rb +0 -9
- data/spec/fixtures/middleware/internal_info_setter.rb +0 -11
- data/spec/fixtures/middleware/public_info_setter.rb +0 -11
- data/spec/fixtures/tasks/Rakefile +0 -15
- data/spec/helper_spec.rb +0 -163
- data/spec/integration_spec.rb +0 -132
- data/spec/middleware_spec.rb +0 -181
- data/spec/notification_spec.rb +0 -877
- data/spec/rack_spec.rb +0 -56
- data/spec/spec_helper.rb +0 -53
@@ -1,24 +1,37 @@
|
|
1
1
|
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Extracts and attaches rack data to an error report
|
2
4
|
class RackRequest
|
5
|
+
SPOOF = "[SPOOF]".freeze
|
6
|
+
COOKIE_HEADER = "Cookie".freeze
|
7
|
+
|
3
8
|
def initialize(bugsnag)
|
4
9
|
@bugsnag = bugsnag
|
5
10
|
end
|
6
11
|
|
7
|
-
def call(
|
8
|
-
if
|
9
|
-
env =
|
12
|
+
def call(report)
|
13
|
+
if report.request_data[:rack_env]
|
14
|
+
env = report.request_data[:rack_env]
|
10
15
|
|
11
16
|
request = ::Rack::Request.new(env)
|
12
17
|
|
13
|
-
params =
|
18
|
+
params =
|
19
|
+
# if the request body isn't rewindable then we can't read request.POST
|
20
|
+
# which is used internally by request.params
|
21
|
+
if request.body.respond_to?(:rewind)
|
22
|
+
request.params rescue {}
|
23
|
+
else
|
24
|
+
request.GET rescue {}
|
25
|
+
end
|
14
26
|
|
27
|
+
client_ip = request.ip.to_s rescue SPOOF
|
15
28
|
session = env["rack.session"]
|
16
29
|
|
17
|
-
# Set the context
|
18
|
-
|
30
|
+
# Set the automatic context
|
31
|
+
report.automatic_context = "#{request.request_method} #{request.path}"
|
19
32
|
|
20
33
|
# Set a sensible default for user_id
|
21
|
-
|
34
|
+
report.user["id"] = request.ip
|
22
35
|
|
23
36
|
# Build the clean url (hide the port if it is obvious)
|
24
37
|
url = "#{request.scheme}://#{request.host}"
|
@@ -26,53 +39,136 @@ module Bugsnag::Middleware
|
|
26
39
|
|
27
40
|
# If app is passed a bad URL, this code will crash attempting to clean it
|
28
41
|
begin
|
29
|
-
url << Bugsnag
|
42
|
+
url << Bugsnag.cleaner.clean_url(request.fullpath)
|
30
43
|
rescue StandardError => stde
|
31
|
-
Bugsnag.
|
44
|
+
Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.fullpath: #{stde}"
|
32
45
|
end
|
33
46
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
|
40
|
-
header_key = key
|
41
|
-
else
|
42
|
-
next
|
43
|
-
end
|
44
|
-
|
45
|
-
headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
|
47
|
+
referer = nil
|
48
|
+
begin
|
49
|
+
referer = Bugsnag.cleaner.clean_url(request.referer) if request.referer
|
50
|
+
rescue StandardError => stde
|
51
|
+
Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.referer: #{stde}"
|
46
52
|
end
|
47
53
|
|
48
54
|
# Add a request tab
|
49
|
-
|
55
|
+
report.add_tab(:request, {
|
50
56
|
:url => url,
|
51
57
|
:httpMethod => request.request_method,
|
52
58
|
:params => params.to_hash,
|
53
|
-
:referer =>
|
54
|
-
:clientIp =>
|
55
|
-
:headers =>
|
59
|
+
:referer => referer,
|
60
|
+
:clientIp => client_ip,
|
61
|
+
:headers => format_headers(env, referer)
|
56
62
|
})
|
57
63
|
|
64
|
+
# add the HTTP version if present
|
65
|
+
if env["SERVER_PROTOCOL"]
|
66
|
+
report.add_metadata(:request, :httpVersion, env["SERVER_PROTOCOL"])
|
67
|
+
end
|
68
|
+
|
69
|
+
add_request_body(report, request, env)
|
70
|
+
add_cookies(report, request)
|
71
|
+
|
58
72
|
# Add an environment tab
|
59
|
-
if
|
60
|
-
|
73
|
+
if report.configuration.send_environment
|
74
|
+
report.add_tab(:environment, env)
|
61
75
|
end
|
62
76
|
|
63
77
|
# Add a session tab
|
64
78
|
if session
|
65
79
|
if session.is_a?(Hash)
|
66
80
|
# Rails 3
|
67
|
-
|
81
|
+
report.add_tab(:session, session)
|
68
82
|
elsif session.respond_to?(:to_hash)
|
69
83
|
# Rails 4
|
70
|
-
|
84
|
+
report.add_tab(:session, session.to_hash)
|
71
85
|
end
|
72
86
|
end
|
73
87
|
end
|
74
88
|
|
75
|
-
@bugsnag.call(
|
89
|
+
@bugsnag.call(report)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def format_headers(env, referer)
|
95
|
+
headers = {}
|
96
|
+
|
97
|
+
env.each_pair do |key, value|
|
98
|
+
if key.to_s.start_with?("HTTP_")
|
99
|
+
header_key = key[5..-1]
|
100
|
+
elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
|
101
|
+
header_key = key
|
102
|
+
else
|
103
|
+
next
|
104
|
+
end
|
105
|
+
|
106
|
+
headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
|
107
|
+
end
|
108
|
+
|
109
|
+
headers["Referer"] = referer if headers["Referer"]
|
110
|
+
|
111
|
+
headers
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_request_body(report, request, env)
|
115
|
+
begin
|
116
|
+
body = parsed_request_body(request, env)
|
117
|
+
rescue StandardError
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
# this request may not have a body
|
122
|
+
return unless body.is_a?(Hash) && !body.empty?
|
123
|
+
|
124
|
+
report.add_metadata(:request, :body, body)
|
125
|
+
end
|
126
|
+
|
127
|
+
def parsed_request_body(request, env)
|
128
|
+
# if the request is not rewindable then either:
|
129
|
+
# - it's been read already and so is impossible to read
|
130
|
+
# - it hasn't been read yet and us reading it will prevent the user from
|
131
|
+
# reading it themselves
|
132
|
+
# in either case we should avoid attempting to
|
133
|
+
return nil unless request.body.respond_to?(:rewind)
|
134
|
+
|
135
|
+
if request.form_data?
|
136
|
+
begin
|
137
|
+
return request.POST
|
138
|
+
ensure
|
139
|
+
request.body.rewind
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
content_type = env["CONTENT_TYPE"]
|
144
|
+
|
145
|
+
return nil if content_type.nil?
|
146
|
+
return nil unless content_type.include?('/json') || content_type.include?('+json')
|
147
|
+
|
148
|
+
begin
|
149
|
+
body = request.body
|
150
|
+
|
151
|
+
JSON.parse(body.read)
|
152
|
+
ensure
|
153
|
+
# the body must be rewound so other things can read it after we do
|
154
|
+
body.rewind
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_cookies(report, request)
|
159
|
+
return unless record_cookies?
|
160
|
+
|
161
|
+
cookies = request.cookies rescue nil
|
162
|
+
|
163
|
+
return unless cookies.is_a?(Hash) && !cookies.empty?
|
164
|
+
|
165
|
+
report.add_metadata(:request, :cookies, cookies)
|
166
|
+
end
|
167
|
+
|
168
|
+
def record_cookies?
|
169
|
+
# only record cookies in the request if none of the filters match "Cookie"
|
170
|
+
# the "Cookie" header will be filtered as normal
|
171
|
+
!Bugsnag.cleaner.filters_match?(COOKIE_HEADER)
|
76
172
|
end
|
77
173
|
end
|
78
174
|
end
|
@@ -1,42 +1,40 @@
|
|
1
1
|
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Extracts and attaches rails and rack environment data to an error report
|
2
4
|
class Rails3Request
|
5
|
+
SPOOF = "[SPOOF]".freeze
|
6
|
+
|
3
7
|
def initialize(bugsnag)
|
4
8
|
@bugsnag = bugsnag
|
5
9
|
end
|
6
10
|
|
7
|
-
def call(
|
8
|
-
if
|
9
|
-
env =
|
11
|
+
def call(report)
|
12
|
+
if report.request_data[:rack_env]
|
13
|
+
env = report.request_data[:rack_env]
|
10
14
|
params = env["action_dispatch.request.parameters"]
|
15
|
+
client_ip = env["action_dispatch.remote_ip"].to_s rescue SPOOF
|
11
16
|
|
12
17
|
if params
|
13
|
-
# Set the context
|
14
|
-
|
18
|
+
# Set the automatic context
|
19
|
+
report.automatic_context = "#{params[:controller]}##{params[:action]}"
|
15
20
|
|
16
21
|
# Augment the request tab
|
17
|
-
|
22
|
+
report.add_tab(:request, {
|
18
23
|
:railsAction => "#{params[:controller]}##{params[:action]}",
|
19
24
|
:params => params
|
20
25
|
})
|
21
26
|
end
|
22
27
|
|
23
28
|
# Use action_dispatch.remote_ip for IP address fields and send request id
|
24
|
-
|
25
|
-
:clientIp =>
|
29
|
+
report.add_tab(:request, {
|
30
|
+
:clientIp => client_ip,
|
26
31
|
:requestId => env["action_dispatch.request_id"]
|
27
32
|
})
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
# Add the rails version
|
32
|
-
if notification.configuration.send_environment
|
33
|
-
notification.add_tab(:environment, {
|
34
|
-
:railsVersion => Rails::VERSION::STRING
|
35
|
-
})
|
36
|
-
end
|
34
|
+
report.user["id"] = client_ip
|
37
35
|
end
|
38
36
|
|
39
|
-
@bugsnag.call(
|
37
|
+
@bugsnag.call(report)
|
40
38
|
end
|
41
39
|
end
|
42
40
|
end
|
@@ -1,23 +1,25 @@
|
|
1
1
|
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Extracts and attaches rake task information to an error report
|
2
4
|
class Rake
|
3
5
|
def initialize(bugsnag)
|
4
6
|
@bugsnag = bugsnag
|
5
7
|
end
|
6
8
|
|
7
|
-
def call(
|
8
|
-
task =
|
9
|
+
def call(report)
|
10
|
+
task = report.request_data[:bugsnag_running_task]
|
9
11
|
|
10
12
|
if task
|
11
|
-
|
13
|
+
report.add_tab(:rake_task, {
|
12
14
|
:name => task.name,
|
13
15
|
:description => task.full_comment,
|
14
16
|
:arguments => task.arg_description
|
15
17
|
})
|
16
18
|
|
17
|
-
|
19
|
+
report.automatic_context ||= task.name
|
18
20
|
end
|
19
21
|
|
20
|
-
@bugsnag.call(
|
22
|
+
@bugsnag.call(report)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Attaches information about current session to an error report
|
4
|
+
class SessionData
|
5
|
+
def initialize(bugsnag)
|
6
|
+
@bugsnag = bugsnag
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(report)
|
10
|
+
session = Bugsnag::SessionTracker.get_current_session
|
11
|
+
|
12
|
+
if session && !session[:paused?]
|
13
|
+
if report.unhandled
|
14
|
+
session[:events][:unhandled] += 1
|
15
|
+
else
|
16
|
+
session[:events][:handled] += 1
|
17
|
+
end
|
18
|
+
|
19
|
+
report.session = session
|
20
|
+
end
|
21
|
+
|
22
|
+
@bugsnag.call(report)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,13 +1,18 @@
|
|
1
1
|
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Attaches Sidekiq job information to an error report
|
2
4
|
class Sidekiq
|
3
5
|
def initialize(bugsnag)
|
4
6
|
@bugsnag = bugsnag
|
5
7
|
end
|
6
8
|
|
7
|
-
def call(
|
8
|
-
sidekiq =
|
9
|
-
|
10
|
-
|
9
|
+
def call(report)
|
10
|
+
sidekiq = report.request_data[:sidekiq]
|
11
|
+
if sidekiq
|
12
|
+
report.add_tab(:sidekiq, sidekiq)
|
13
|
+
report.automatic_context ||= "#{sidekiq[:msg]['wrapped'] || sidekiq[:msg]['class']}@#{sidekiq[:msg]['queue']}"
|
14
|
+
end
|
15
|
+
@bugsnag.call(report)
|
11
16
|
end
|
12
17
|
end
|
13
18
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Attaches any "Did you mean?" suggestions to the report
|
4
|
+
class SuggestionData
|
5
|
+
|
6
|
+
CAPTURE_REGEX = /Did you mean\?([\s\S]+)$/
|
7
|
+
DELIMITER = "\n"
|
8
|
+
|
9
|
+
def initialize(bugsnag)
|
10
|
+
@bugsnag = bugsnag
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(event)
|
14
|
+
matches = []
|
15
|
+
|
16
|
+
event.errors.each do |error|
|
17
|
+
match = CAPTURE_REGEX.match(error.error_message)
|
18
|
+
|
19
|
+
next unless match
|
20
|
+
|
21
|
+
suggestions = match.captures[0].split(DELIMITER)
|
22
|
+
matches.concat(suggestions.map(&:strip))
|
23
|
+
end
|
24
|
+
|
25
|
+
if matches.size == 1
|
26
|
+
event.add_metadata(:error, { suggestion: matches.first })
|
27
|
+
elsif matches.size > 1
|
28
|
+
event.add_metadata(:error, { suggestions: matches })
|
29
|
+
end
|
30
|
+
|
31
|
+
@bugsnag.call(event)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
module Bugsnag::Middleware
|
2
|
+
##
|
3
|
+
# Extracts and attaches user information from Warden to an error report
|
2
4
|
class WardenUser
|
3
5
|
SCOPE_PATTERN = /^warden\.user\.([^.]+)\.key$/
|
4
6
|
COMMON_USER_FIELDS = [:email, :name, :first_name, :last_name, :created_at, :id]
|
@@ -7,9 +9,9 @@ module Bugsnag::Middleware
|
|
7
9
|
@bugsnag = bugsnag
|
8
10
|
end
|
9
11
|
|
10
|
-
def call(
|
11
|
-
if
|
12
|
-
env =
|
12
|
+
def call(report)
|
13
|
+
if report.request_data[:rack_env] && report.request_data[:rack_env]["warden"]
|
14
|
+
env = report.request_data[:rack_env]
|
13
15
|
session = env["rack.session"] || {}
|
14
16
|
|
15
17
|
# Find all warden user scopes
|
@@ -21,7 +23,10 @@ module Bugsnag::Middleware
|
|
21
23
|
# Extract useful user information
|
22
24
|
user = {}
|
23
25
|
user_object = env["warden"].user({:scope => best_scope, :run_callbacks => false}) rescue nil
|
26
|
+
|
24
27
|
if user_object
|
28
|
+
user[:warden_scope] = best_scope
|
29
|
+
|
25
30
|
# Build the user info for this scope
|
26
31
|
COMMON_USER_FIELDS.each do |field|
|
27
32
|
user[field] = user_object.send(field) if user_object.respond_to?(field)
|
@@ -29,11 +34,11 @@ module Bugsnag::Middleware
|
|
29
34
|
end
|
30
35
|
|
31
36
|
# We merge the first warden scope down, so that it is the main "user" for the request
|
32
|
-
|
37
|
+
report.user = user unless user.empty?
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
36
|
-
@bugsnag.call(
|
41
|
+
@bugsnag.call(report)
|
37
42
|
end
|
38
43
|
end
|
39
|
-
end
|
44
|
+
end
|
@@ -1,11 +1,19 @@
|
|
1
|
+
require "bugsnag/on_error_callbacks"
|
2
|
+
|
1
3
|
module Bugsnag
|
2
4
|
class MiddlewareStack
|
5
|
+
##
|
6
|
+
# Creates the middleware stack.
|
3
7
|
def initialize
|
4
8
|
@middlewares = []
|
5
9
|
@disabled_middleware = []
|
6
10
|
@mutex = Mutex.new
|
7
11
|
end
|
8
12
|
|
13
|
+
##
|
14
|
+
# Defines a new middleware to use in the middleware call sequence.
|
15
|
+
#
|
16
|
+
# Will return early if given middleware is disabled or already included.
|
9
17
|
def use(new_middleware)
|
10
18
|
@mutex.synchronize do
|
11
19
|
return if @disabled_middleware.include?(new_middleware)
|
@@ -15,6 +23,11 @@ module Bugsnag
|
|
15
23
|
end
|
16
24
|
end
|
17
25
|
|
26
|
+
##
|
27
|
+
# Inserts a new middleware to use after a given middleware already added.
|
28
|
+
#
|
29
|
+
# Will return early if given middleware is disabled or already added.
|
30
|
+
# New middleware will be inserted last if the existing middleware is not already included.
|
18
31
|
def insert_after(after, new_middleware)
|
19
32
|
@mutex.synchronize do
|
20
33
|
return if @disabled_middleware.include?(new_middleware)
|
@@ -34,6 +47,11 @@ module Bugsnag
|
|
34
47
|
end
|
35
48
|
end
|
36
49
|
|
50
|
+
##
|
51
|
+
# Inserts a new middleware to use before a given middleware already added.
|
52
|
+
#
|
53
|
+
# Will return early if given middleware is disabled or already added.
|
54
|
+
# New middleware will be inserted last if the existing middleware is not already included.
|
37
55
|
def insert_before(before, new_middleware)
|
38
56
|
@mutex.synchronize do
|
39
57
|
return if @disabled_middleware.include?(new_middleware)
|
@@ -49,21 +67,38 @@ module Bugsnag
|
|
49
67
|
end
|
50
68
|
end
|
51
69
|
|
70
|
+
##
|
71
|
+
# Disable the given middleware. This removes them from the list of
|
72
|
+
# middleware and ensures they cannot be added again
|
73
|
+
#
|
74
|
+
# See also {#remove}
|
52
75
|
def disable(*middlewares)
|
53
76
|
@mutex.synchronize do
|
54
77
|
@disabled_middleware += middlewares
|
55
78
|
|
56
|
-
@middlewares.delete_if {|m| @disabled_middleware.include?(m)}
|
79
|
+
@middlewares.delete_if {|m| @disabled_middleware.include?(m) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Remove the given middleware from the list of middleware
|
85
|
+
#
|
86
|
+
# This is like {#disable} but allows the middleware to be added again
|
87
|
+
def remove(*middlewares)
|
88
|
+
@mutex.synchronize do
|
89
|
+
@middlewares.delete_if {|m| middlewares.include?(m) }
|
57
90
|
end
|
58
91
|
end
|
59
92
|
|
60
|
-
|
93
|
+
##
|
94
|
+
# Allows the user to proxy methods for more complex functionality.
|
61
95
|
def method_missing(method, *args, &block)
|
62
96
|
@middlewares.send(method, *args, &block)
|
63
97
|
end
|
64
98
|
|
65
|
-
|
66
|
-
|
99
|
+
##
|
100
|
+
# Runs the middleware stack.
|
101
|
+
def run(report)
|
67
102
|
# The final lambda is the termination of the middleware stack. It calls deliver on the notification
|
68
103
|
lambda_has_run = false
|
69
104
|
notify_lambda = lambda do |notif|
|
@@ -73,26 +108,44 @@ module Bugsnag
|
|
73
108
|
|
74
109
|
begin
|
75
110
|
# We reverse them, so we can call "call" on the first middleware
|
76
|
-
middleware_procs.reverse.inject(notify_lambda) {
|
111
|
+
middleware_procs.reverse.inject(notify_lambda) {|n, e| e.call(n) }.call(report)
|
77
112
|
rescue StandardError => e
|
78
113
|
# KLUDGE: Since we don't re-raise middleware exceptions, this breaks rspec
|
79
114
|
raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
|
80
115
|
|
81
116
|
# We dont notify, as we dont want to loop forever in the case of really broken middleware, we will
|
82
117
|
# still send this notify
|
83
|
-
Bugsnag.warn "Bugsnag middleware error: #{e}"
|
84
|
-
Bugsnag.
|
118
|
+
Bugsnag.configuration.warn "Bugsnag middleware error: #{e}"
|
119
|
+
Bugsnag.configuration.warn "Middleware error stacktrace: #{e.backtrace.inspect}"
|
85
120
|
end
|
86
121
|
|
87
122
|
# Ensure that the deliver has been performed, and no middleware has botched it
|
88
|
-
notify_lambda.call(
|
123
|
+
notify_lambda.call(report) unless lambda_has_run
|
89
124
|
end
|
90
125
|
|
91
126
|
private
|
127
|
+
|
128
|
+
##
|
92
129
|
# Generates a list of middleware procs that are ready to be run
|
93
130
|
# Pass each one a reference to the next in the queue
|
131
|
+
#
|
132
|
+
# @return [Array<Proc>]
|
94
133
|
def middleware_procs
|
95
|
-
|
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
|
+
|
137
|
+
# Wrap the classes in a proc that, when called, news up the middleware and
|
138
|
+
# passes the next middleware in the queue
|
139
|
+
middleware_instances = classes.map do |middleware|
|
140
|
+
proc {|next_middleware| middleware.new(next_middleware) }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Wrap the list of callables in a proc that, when called, wraps them in an
|
144
|
+
# 'OnErrorCallbacks' instance that also has a reference to the next middleware
|
145
|
+
wrapped_callables = proc {|next_middleware| OnErrorCallbacks.new(next_middleware, callables) }
|
146
|
+
|
147
|
+
# Return the combined middleware and wrapped callables
|
148
|
+
middleware_instances.push(wrapped_callables)
|
96
149
|
end
|
97
150
|
end
|
98
151
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bugsnag
|
2
|
+
# @api private
|
3
|
+
class OnErrorCallbacks
|
4
|
+
def initialize(next_middleware, callbacks)
|
5
|
+
@next_middleware = next_middleware
|
6
|
+
@callbacks = callbacks
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# @param report [Report]
|
11
|
+
def call(report)
|
12
|
+
@callbacks.each do |callback|
|
13
|
+
begin
|
14
|
+
should_continue = callback.call(report)
|
15
|
+
rescue StandardError => e
|
16
|
+
Bugsnag.configuration.warn("Error occurred in on_error callback: '#{e}'")
|
17
|
+
Bugsnag.configuration.warn("on_error callback stacktrace: #{e.backtrace.inspect}")
|
18
|
+
end
|
19
|
+
|
20
|
+
# If a callback returns false, we ignore the report and stop running callbacks
|
21
|
+
# Note that we explicitly check for 'false' so that callbacks don't need
|
22
|
+
# to return anything (i.e. can return 'nil') and we still continue
|
23
|
+
next unless should_continue == false
|
24
|
+
|
25
|
+
report.ignore!
|
26
|
+
|
27
|
+
break
|
28
|
+
end
|
29
|
+
|
30
|
+
@next_middleware.call(report)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|