airbrake 9.4.0 → 11.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/airbrake.rb +2 -0
- data/lib/airbrake/capistrano.rb +2 -0
- data/lib/airbrake/capistrano/capistrano2.rb +2 -0
- data/lib/airbrake/capistrano/capistrano3.rb +3 -1
- data/lib/airbrake/delayed_job.rb +24 -6
- data/lib/airbrake/logger.rb +2 -0
- data/lib/airbrake/rack.rb +4 -0
- data/lib/airbrake/rack/context_filter.rb +9 -2
- data/lib/airbrake/rack/http_headers_filter.rb +7 -5
- data/lib/airbrake/rack/http_params_filter.rb +2 -0
- data/lib/airbrake/rack/instrumentable.rb +112 -4
- data/lib/airbrake/rack/middleware.rb +3 -1
- data/lib/airbrake/rack/request_body_filter.rb +2 -0
- data/lib/airbrake/rack/request_store.rb +2 -0
- data/lib/airbrake/rack/route_filter.rb +8 -10
- data/lib/airbrake/rack/session_filter.rb +2 -0
- data/lib/airbrake/rack/user.rb +9 -1
- data/lib/airbrake/rack/user_filter.rb +2 -0
- data/lib/airbrake/rails.rb +14 -135
- data/lib/airbrake/rails/action_cable.rb +2 -0
- data/lib/airbrake/rails/action_cable/notify_callback.rb +2 -0
- data/lib/airbrake/rails/action_controller.rb +9 -4
- data/lib/airbrake/rails/action_controller_notify_subscriber.rb +6 -2
- data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +6 -1
- data/lib/airbrake/rails/action_controller_route_subscriber.rb +8 -15
- data/lib/airbrake/rails/active_job.rb +25 -8
- data/lib/airbrake/rails/active_record.rb +2 -0
- data/lib/airbrake/rails/active_record_subscriber.rb +7 -3
- data/lib/airbrake/rails/app.rb +64 -19
- data/lib/airbrake/rails/backtrace_cleaner.rb +13 -0
- data/lib/airbrake/rails/curb.rb +19 -22
- data/lib/airbrake/rails/event.rb +13 -7
- data/lib/airbrake/rails/excon_subscriber.rb +4 -0
- data/lib/airbrake/rails/http.rb +2 -0
- data/lib/airbrake/rails/http_client.rb +12 -6
- data/lib/airbrake/rails/net_http.rb +14 -6
- data/lib/airbrake/rails/railtie.rb +151 -0
- data/lib/airbrake/rails/typhoeus.rb +11 -7
- data/lib/airbrake/rake.rb +3 -1
- data/lib/airbrake/rake/tasks.rb +7 -5
- data/lib/airbrake/resque.rb +32 -0
- data/lib/airbrake/shoryuken.rb +17 -3
- data/lib/airbrake/sidekiq.rb +22 -14
- data/lib/airbrake/sidekiq/retryable_jobs_filter.rb +2 -0
- data/lib/airbrake/sneakers.rb +39 -1
- data/lib/airbrake/version.rb +3 -1
- data/lib/generators/airbrake_generator.rb +2 -0
- data/lib/generators/airbrake_initializer.rb.erb +6 -12
- metadata +62 -76
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Airbrake
|
2
4
|
module Rails
|
3
5
|
# Contains helper methods that can be used inside Rails controllers to send
|
@@ -10,23 +12,26 @@ module Airbrake
|
|
10
12
|
# A helper method for sending notices to Airbrake *asynchronously*.
|
11
13
|
# Attaches information from the Rack env.
|
12
14
|
# @see Airbrake#notify, #notify_airbrake_sync
|
13
|
-
def notify_airbrake(exception, params = {})
|
15
|
+
def notify_airbrake(exception, params = {}, &block)
|
14
16
|
return unless (notice = build_notice(exception, params))
|
15
|
-
|
17
|
+
|
18
|
+
Airbrake.notify(notice, params, &block)
|
16
19
|
end
|
17
20
|
|
18
21
|
# A helper method for sending notices to Airbrake *synchronously*.
|
19
22
|
# Attaches information from the Rack env.
|
20
23
|
# @see Airbrake#notify_sync, #notify_airbrake
|
21
|
-
def notify_airbrake_sync(exception, params = {})
|
24
|
+
def notify_airbrake_sync(exception, params = {}, &block)
|
22
25
|
return unless (notice = build_notice(exception, params))
|
23
|
-
|
26
|
+
|
27
|
+
Airbrake.notify_sync(notice, params, &block)
|
24
28
|
end
|
25
29
|
|
26
30
|
# @param [Exception] exception
|
27
31
|
# @return [Airbrake::Notice] the notice with information from the Rack env
|
28
32
|
def build_notice(exception, params = {})
|
29
33
|
return unless (notice = Airbrake.build_notice(exception, params))
|
34
|
+
|
30
35
|
notice.stash[:rack_request] = request
|
31
36
|
notice
|
32
37
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'airbrake/rails/event'
|
2
4
|
|
3
5
|
module Airbrake
|
@@ -8,6 +10,8 @@ module Airbrake
|
|
8
10
|
# @since v8.0.0
|
9
11
|
class ActionControllerNotifySubscriber
|
10
12
|
def call(*args)
|
13
|
+
return unless Airbrake::Config.instance.performance_stats
|
14
|
+
|
11
15
|
routes = Airbrake::Rack::RequestStore[:routes]
|
12
16
|
return if !routes || routes.none?
|
13
17
|
|
@@ -18,8 +22,8 @@ module Airbrake
|
|
18
22
|
method: event.method,
|
19
23
|
route: route,
|
20
24
|
status_code: event.status_code,
|
21
|
-
|
22
|
-
|
25
|
+
timing: event.duration,
|
26
|
+
time: event.time,
|
23
27
|
)
|
24
28
|
end
|
25
29
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'airbrake/rails/event'
|
2
4
|
|
3
5
|
module Airbrake
|
@@ -5,6 +7,8 @@ module Airbrake
|
|
5
7
|
# @since v8.3.0
|
6
8
|
class ActionControllerPerformanceBreakdownSubscriber
|
7
9
|
def call(*args)
|
10
|
+
return unless Airbrake::Config.instance.performance_stats
|
11
|
+
|
8
12
|
routes = Airbrake::Rack::RequestStore[:routes]
|
9
13
|
return if !routes || routes.none?
|
10
14
|
|
@@ -20,7 +24,8 @@ module Airbrake
|
|
20
24
|
route: route,
|
21
25
|
response_type: event.response_type,
|
22
26
|
groups: groups,
|
23
|
-
|
27
|
+
timing: event.duration,
|
28
|
+
time: event.time,
|
24
29
|
}
|
25
30
|
|
26
31
|
Airbrake.notify_performance_breakdown(breakdown_info, stash)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'airbrake/rails/event'
|
2
4
|
require 'airbrake/rails/app'
|
3
5
|
|
@@ -8,33 +10,24 @@ module Airbrake
|
|
8
10
|
#
|
9
11
|
# @since v8.0.0
|
10
12
|
class ActionControllerRouteSubscriber
|
11
|
-
def initialize
|
12
|
-
@app = Airbrake::Rails::App.new
|
13
|
-
end
|
14
|
-
|
15
13
|
def call(*args)
|
14
|
+
return unless Airbrake::Config.instance.performance_stats
|
15
|
+
|
16
16
|
# We don't track routeless events.
|
17
17
|
return unless (routes = Airbrake::Rack::RequestStore[:routes])
|
18
18
|
|
19
19
|
event = Airbrake::Rails::Event.new(*args)
|
20
|
-
route =
|
20
|
+
route = Airbrake::Rails::App.recognize_route(
|
21
|
+
Airbrake::Rack::RequestStore[:request],
|
22
|
+
)
|
21
23
|
return unless route
|
22
24
|
|
23
25
|
routes[route.path] = {
|
24
26
|
method: event.method,
|
25
27
|
response_type: event.response_type,
|
26
|
-
groups: {}
|
28
|
+
groups: {},
|
27
29
|
}
|
28
30
|
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def find_route(params)
|
33
|
-
@app.routes.find do |route|
|
34
|
-
route.controller == params['controller'] &&
|
35
|
-
route.action == params['action']
|
36
|
-
end
|
37
|
-
end
|
38
31
|
end
|
39
32
|
end
|
40
33
|
end
|
@@ -1,18 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Airbrake
|
2
4
|
module Rails
|
3
5
|
# Enables support for exceptions occurring in ActiveJob jobs.
|
4
6
|
module ActiveJob
|
5
7
|
extend ActiveSupport::Concern
|
6
8
|
|
7
|
-
# @return [Array<Regexp>] the list of known adapters
|
8
|
-
ADAPTERS = [/Resque/, /Sidekiq/, /DelayedJob/].freeze
|
9
|
-
|
10
9
|
def self.notify_airbrake(exception, job)
|
11
|
-
queue_adapter = job.class.queue_adapter.to_s
|
12
|
-
|
13
|
-
# Do not notify twice if a queue_adapter is configured already.
|
14
|
-
raise exception if ADAPTERS.any? { |a| a =~ queue_adapter }
|
15
|
-
|
16
10
|
notice = Airbrake.build_notice(exception)
|
17
11
|
notice[:context][:component] = 'active_job'
|
18
12
|
notice[:context][:action] = job.class.name
|
@@ -23,10 +17,33 @@ module Airbrake
|
|
23
17
|
raise exception
|
24
18
|
end
|
25
19
|
|
20
|
+
def self.perform(job, block)
|
21
|
+
timing = Airbrake::Benchmark.measure do
|
22
|
+
block.call
|
23
|
+
end
|
24
|
+
rescue StandardError => exception
|
25
|
+
Airbrake.notify_queue(
|
26
|
+
queue: job.class.name,
|
27
|
+
error_count: 1,
|
28
|
+
timing: 0.01,
|
29
|
+
)
|
30
|
+
raise exception
|
31
|
+
else
|
32
|
+
Airbrake.notify_queue(
|
33
|
+
queue: job.class.name,
|
34
|
+
error_count: 0,
|
35
|
+
timing: timing,
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
26
39
|
included do
|
27
40
|
rescue_from(Exception) do |exception|
|
28
41
|
Airbrake::Rails::ActiveJob.notify_airbrake(exception, self)
|
29
42
|
end
|
43
|
+
|
44
|
+
around_perform do |job, block|
|
45
|
+
Airbrake::Rails::ActiveJob.perform(job, block)
|
46
|
+
end
|
30
47
|
end
|
31
48
|
end
|
32
49
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'airbrake/rails/event'
|
2
4
|
require 'airbrake/rails/backtrace_cleaner'
|
3
5
|
|
@@ -8,6 +10,8 @@ module Airbrake
|
|
8
10
|
# @since v8.1.0
|
9
11
|
class ActiveRecordSubscriber
|
10
12
|
def call(*args)
|
13
|
+
return unless Airbrake::Config.instance.query_stats
|
14
|
+
|
11
15
|
routes = Airbrake::Rack::RequestStore[:routes]
|
12
16
|
return if !routes || routes.none?
|
13
17
|
|
@@ -22,8 +26,8 @@ module Airbrake
|
|
22
26
|
func: frame[:function],
|
23
27
|
file: frame[:file],
|
24
28
|
line: frame[:line],
|
25
|
-
|
26
|
-
|
29
|
+
timing: event.duration,
|
30
|
+
time: event.time,
|
27
31
|
)
|
28
32
|
end
|
29
33
|
end
|
@@ -33,7 +37,7 @@ module Airbrake
|
|
33
37
|
def last_caller
|
34
38
|
exception = StandardError.new
|
35
39
|
exception.set_backtrace(
|
36
|
-
Airbrake::Rails::BacktraceCleaner.clean(Kernel.caller)
|
40
|
+
Airbrake::Rails::BacktraceCleaner.clean(Kernel.caller),
|
37
41
|
)
|
38
42
|
Airbrake::Backtrace.parse(exception).first || {}
|
39
43
|
end
|
data/lib/airbrake/rails/app.rb
CHANGED
@@ -1,33 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Airbrake
|
2
4
|
module Rails
|
3
|
-
# App is a wrapper around Rails.application
|
5
|
+
# App is a wrapper around Rails.application.
|
4
6
|
#
|
5
7
|
# @since v9.0.3
|
6
8
|
# @api private
|
7
9
|
class App
|
8
|
-
Route = Struct.new(:path
|
9
|
-
|
10
|
-
def routes
|
11
|
-
@routes ||= [*app_routes, *engine_routes].map do |route|
|
12
|
-
Route.new(
|
13
|
-
route.path.spec.to_s,
|
14
|
-
route.defaults[:controller],
|
15
|
-
route.defaults[:action]
|
16
|
-
)
|
17
|
-
end
|
18
|
-
end
|
10
|
+
Route = Struct.new(:path)
|
19
11
|
|
20
|
-
|
12
|
+
# @param [] request
|
13
|
+
# @return [Airbrake::Rails::App::Route, nil]
|
14
|
+
# rubocop:disable Metrics/AbcSize
|
15
|
+
def self.recognize_route(request)
|
16
|
+
# Duplicate `request` because `recognize` *can* strip the request's
|
17
|
+
# `path_info`, which results in broken engine links (when the engine has
|
18
|
+
# an isolated namespace).
|
19
|
+
request_copy = request.dup
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
# Save original script name because `router.recognize(request)` mutates
|
22
|
+
# it. It's a Rails bug. More info in:
|
23
|
+
# * https://github.com/airbrake/airbrake/issues/1072
|
24
|
+
# * https://github.com/rails/rails/issues/31152
|
25
|
+
original_script_name = request.env['SCRIPT_NAME']
|
26
|
+
|
27
|
+
# We must search every engine individually to find a concrete route. If
|
28
|
+
# we rely only on the `Rails.application.routes.router`, then the
|
29
|
+
# recognize call would return the root route, neglecting PATH_INFO
|
30
|
+
# completely. For example:
|
31
|
+
# * a request is made to `marketing#pricing`
|
32
|
+
# * `Rails.application` recognizes it as `marketing#/` (incorrect)
|
33
|
+
# * `Marketing::Engine` recognizes it as `marketing#/pricing` (correct)
|
34
|
+
engines.each do |engine|
|
35
|
+
engine.routes.router.recognize(request_copy) do |route, _params|
|
36
|
+
# Restore original script name. Remove this code when/if the Rails
|
37
|
+
# bug is fixed: https://github.com/airbrake/airbrake/issues/1072
|
38
|
+
request.env['SCRIPT_NAME'] = original_script_name
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
40
|
+
# Skip "catch-all" routes such as:
|
41
|
+
# get '*path => 'pages#about'
|
42
|
+
#
|
43
|
+
# @todo The `glob?` method was added in Rails v4.2.0.beta1. We
|
44
|
+
# should remove the `respond_to?` check once we drop old Rails
|
45
|
+
# versions support.
|
46
|
+
#
|
47
|
+
# https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f
|
48
|
+
next if route.respond_to?(:glob?) && route.glob?
|
49
|
+
|
50
|
+
path =
|
51
|
+
if engine == ::Rails.application
|
52
|
+
route.path.spec.to_s
|
53
|
+
else
|
54
|
+
"#{engine.engine_name}##{route.path.spec}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Rails can recognize multiple routes for the given request. For
|
58
|
+
# example, if we visit /users/2/edit, then Rails sees these routes:
|
59
|
+
# * "/users/:id/edit(.:format)"
|
60
|
+
# * "/"
|
61
|
+
#
|
62
|
+
# We return the first route as, what it seems, the most optimal
|
63
|
+
# approach.
|
64
|
+
return Route.new(path)
|
65
|
+
end
|
29
66
|
end
|
67
|
+
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
# rubocop:enable Metrics/AbcSize
|
71
|
+
|
72
|
+
def self.engines
|
73
|
+
@engines ||= [*::Rails::Engine.subclasses, ::Rails.application]
|
30
74
|
end
|
75
|
+
private_class_method :engines
|
31
76
|
end
|
32
77
|
end
|
33
78
|
end
|
@@ -1,10 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Airbrake
|
2
4
|
module Rails
|
3
5
|
# BacktraceCleaner is a wrapper around Rails.backtrace_cleaner.
|
4
6
|
class BacktraceCleaner
|
7
|
+
# @return [Regexp]
|
8
|
+
AIRBRAKE_FRAME_PATTERN = %r{/airbrake/lib/airbrake/}.freeze
|
9
|
+
|
5
10
|
def self.clean(backtrace)
|
6
11
|
::Rails.backtrace_cleaner.clean(backtrace).first(1)
|
7
12
|
end
|
8
13
|
end
|
9
14
|
end
|
10
15
|
end
|
16
|
+
|
17
|
+
if defined?(Rails)
|
18
|
+
# Silence own frames to let the cleaner proceed to the next line (and probably
|
19
|
+
# find the correct call-site coming from the app code rather this library).
|
20
|
+
Rails.backtrace_cleaner.add_silencer do |line|
|
21
|
+
line =~ Airbrake::Rails::BacktraceCleaner::AIRBRAKE_FRAME_PATTERN
|
22
|
+
end
|
23
|
+
end
|
data/lib/airbrake/rails/curb.rb
CHANGED
@@ -1,35 +1,32 @@
|
|
1
|
-
|
2
|
-
# Monkey-patch to measure request timing.
|
3
|
-
class Easy
|
4
|
-
alias http_without_airbrake http
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module Airbrake
|
4
|
+
module Rails
|
5
|
+
# Allows measuring request timing.
|
6
|
+
module CurlEasy
|
7
|
+
def http(verb)
|
8
|
+
Airbrake::Rack.capture_timing(:http) do
|
9
|
+
super(verb)
|
10
|
+
end
|
9
11
|
end
|
10
|
-
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
perform_without_airbrake(&block)
|
13
|
+
def perform(&block)
|
14
|
+
Airbrake::Rack.capture_timing(:http) do
|
15
|
+
super(&block)
|
16
|
+
end
|
17
17
|
end
|
18
18
|
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module Curl
|
23
|
-
# Monkey-patch to measure request timing.
|
24
|
-
class Multi
|
25
|
-
class << self
|
26
|
-
alias http_without_airbrake http
|
27
19
|
|
20
|
+
# Allows measuring request timing.
|
21
|
+
module CurlMulti
|
28
22
|
def http(urls_with_config, multi_options = {}, &block)
|
29
23
|
Airbrake::Rack.capture_timing(:http) do
|
30
|
-
|
24
|
+
super(urls_with_config, multi_options, &block)
|
31
25
|
end
|
32
26
|
end
|
33
27
|
end
|
34
28
|
end
|
35
29
|
end
|
30
|
+
|
31
|
+
Curl::Easy.prepend(Airbrake::Rails::CurlEasy)
|
32
|
+
Curl::Multi.singleton_class.prepend(Airbrake::Rails::CurlMulti)
|
data/lib/airbrake/rails/event.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Airbrake
|
2
4
|
module Rails
|
3
5
|
# Event is a wrapper around ActiveSupport::Notifications::Event.
|
@@ -6,7 +8,9 @@ module Airbrake
|
|
6
8
|
# @api private
|
7
9
|
class Event
|
8
10
|
# @see https://github.com/rails/rails/issues/8987
|
9
|
-
HTML_RESPONSE_WILDCARD = "*/*"
|
11
|
+
HTML_RESPONSE_WILDCARD = "*/*"
|
12
|
+
|
13
|
+
include Airbrake::Loggable
|
10
14
|
|
11
15
|
def initialize(*args)
|
12
16
|
@event = ActiveSupport::Notifications::Event.new(*args)
|
@@ -41,10 +45,6 @@ module Airbrake
|
|
41
45
|
@event.time
|
42
46
|
end
|
43
47
|
|
44
|
-
def end
|
45
|
-
@event.end
|
46
|
-
end
|
47
|
-
|
48
48
|
def groups
|
49
49
|
groups = {}
|
50
50
|
groups[:db] = db_runtime if db_runtime > 0
|
@@ -57,14 +57,20 @@ module Airbrake
|
|
57
57
|
|
58
58
|
if @event.payload[:exception]
|
59
59
|
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(
|
60
|
-
@event.payload[:exception].first
|
60
|
+
@event.payload[:exception].first,
|
61
61
|
)
|
62
62
|
status = 500 if status == 0
|
63
63
|
|
64
64
|
return status
|
65
65
|
end
|
66
66
|
|
67
|
-
|
67
|
+
# The ActiveSupport event doesn't have status only in two cases:
|
68
|
+
# - an exception was thrown
|
69
|
+
# - unauthorized access
|
70
|
+
# We have already handled the exception so what's left is unauthorized
|
71
|
+
# access. There's no way to know for sure it's unauthorized access, so
|
72
|
+
# we are rather optimistic here.
|
73
|
+
401
|
68
74
|
end
|
69
75
|
|
70
76
|
def duration
|