airbrake 9.5.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 +7 -0
- data/lib/airbrake.rb +30 -0
- data/lib/airbrake/capistrano.rb +6 -0
- data/lib/airbrake/capistrano/capistrano2.rb +38 -0
- data/lib/airbrake/capistrano/capistrano3.rb +21 -0
- data/lib/airbrake/delayed_job.rb +48 -0
- data/lib/airbrake/logger.rb +101 -0
- data/lib/airbrake/rack.rb +35 -0
- data/lib/airbrake/rack/context_filter.rb +58 -0
- data/lib/airbrake/rack/http_headers_filter.rb +42 -0
- data/lib/airbrake/rack/http_params_filter.rb +25 -0
- data/lib/airbrake/rack/instrumentable.rb +28 -0
- data/lib/airbrake/rack/middleware.rb +100 -0
- data/lib/airbrake/rack/request_body_filter.rb +31 -0
- data/lib/airbrake/rack/request_store.rb +32 -0
- data/lib/airbrake/rack/route_filter.rb +53 -0
- data/lib/airbrake/rack/session_filter.rb +23 -0
- data/lib/airbrake/rack/user.rb +70 -0
- data/lib/airbrake/rack/user_filter.rb +23 -0
- data/lib/airbrake/rails.rb +32 -0
- data/lib/airbrake/rails/action_cable.rb +33 -0
- data/lib/airbrake/rails/action_cable/notify_callback.rb +20 -0
- data/lib/airbrake/rails/action_controller.rb +35 -0
- data/lib/airbrake/rails/action_controller_notify_subscriber.rb +28 -0
- data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +46 -0
- data/lib/airbrake/rails/action_controller_route_subscriber.rb +46 -0
- data/lib/airbrake/rails/active_job.rb +33 -0
- data/lib/airbrake/rails/active_record.rb +34 -0
- data/lib/airbrake/rails/active_record_subscriber.rb +42 -0
- data/lib/airbrake/rails/app.rb +43 -0
- data/lib/airbrake/rails/backtrace_cleaner.rb +10 -0
- data/lib/airbrake/rails/curb.rb +35 -0
- data/lib/airbrake/rails/event.rb +83 -0
- data/lib/airbrake/rails/excon_subscriber.rb +21 -0
- data/lib/airbrake/rails/http.rb +12 -0
- data/lib/airbrake/rails/http_client.rb +10 -0
- data/lib/airbrake/rails/net_http.rb +10 -0
- data/lib/airbrake/rails/railtie.rb +141 -0
- data/lib/airbrake/rails/typhoeus.rb +12 -0
- data/lib/airbrake/rake.rb +63 -0
- data/lib/airbrake/rake/tasks.rb +110 -0
- data/lib/airbrake/resque.rb +29 -0
- data/lib/airbrake/shoryuken.rb +40 -0
- data/lib/airbrake/sidekiq.rb +47 -0
- data/lib/airbrake/sidekiq/retryable_jobs_filter.rb +51 -0
- data/lib/airbrake/sneakers.rb +34 -0
- data/lib/airbrake/version.rb +5 -0
- data/lib/generators/airbrake_generator.rb +23 -0
- data/lib/generators/airbrake_initializer.rb.erb +78 -0
- metadata +416 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# Airbrake Rack middleware for Rails and Sinatra applications (or any other
|
4
|
+
# Rack-compliant app). Any errors raised by the upstream application will be
|
5
|
+
# delivered to Airbrake and re-raised.
|
6
|
+
#
|
7
|
+
# The middleware automatically sends information about the framework that
|
8
|
+
# uses it (name and version).
|
9
|
+
#
|
10
|
+
# For Rails apps the middleware collects route performance statistics.
|
11
|
+
class Middleware
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
# Thread-safe {call!}.
|
17
|
+
#
|
18
|
+
# @param [Hash] env the Rack environment
|
19
|
+
# @see https://github.com/airbrake/airbrake/issues/904
|
20
|
+
def call(env)
|
21
|
+
dup.call!(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Rescues any exceptions, sends them to Airbrake and re-raises the
|
25
|
+
# exception. We also duplicate middleware to guarantee thread-safety.
|
26
|
+
#
|
27
|
+
# @param [Hash] env the Rack environment
|
28
|
+
def call!(env)
|
29
|
+
before_call(env)
|
30
|
+
|
31
|
+
begin
|
32
|
+
response = @app.call(env)
|
33
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
34
|
+
notify_airbrake(ex)
|
35
|
+
raise ex
|
36
|
+
end
|
37
|
+
|
38
|
+
exception = framework_exception(env)
|
39
|
+
notify_airbrake(exception) if exception
|
40
|
+
|
41
|
+
response
|
42
|
+
ensure
|
43
|
+
# Clear routes for the next request.
|
44
|
+
RequestStore.clear
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def before_call(env)
|
50
|
+
# Rails hooks such as ActionControllerRouteSubscriber rely on this.
|
51
|
+
RequestStore[:routes] = {}
|
52
|
+
RequestStore[:request] = find_request(env)
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_request(env)
|
56
|
+
if defined?(ActionDispatch::Request)
|
57
|
+
ActionDispatch::Request.new(env)
|
58
|
+
elsif defined?(Sinatra::Request)
|
59
|
+
Sinatra::Request.new(env)
|
60
|
+
else
|
61
|
+
::Rack::Request.new(env)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def notify_airbrake(exception)
|
66
|
+
notice = Airbrake.build_notice(exception)
|
67
|
+
return unless notice
|
68
|
+
|
69
|
+
# ActionDispatch::Request correctly captures server port when using SSL:
|
70
|
+
# See: https://github.com/airbrake/airbrake/issues/802
|
71
|
+
notice.stash[:rack_request] = RequestStore[:request]
|
72
|
+
|
73
|
+
Airbrake.notify(notice)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Web framework middlewares often store rescued exceptions inside the
|
77
|
+
# Rack env, but Rack doesn't have a standard key for it:
|
78
|
+
#
|
79
|
+
# - Rails uses action_dispatch.exception: https://goo.gl/Kd694n
|
80
|
+
# - Sinatra uses sinatra.error: https://goo.gl/LLkVL9
|
81
|
+
# - Goliath uses rack.exception: https://goo.gl/i7e1nA
|
82
|
+
def framework_exception(env)
|
83
|
+
env['action_dispatch.exception'] ||
|
84
|
+
env['sinatra.error'] ||
|
85
|
+
env['rack.exception']
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
[
|
92
|
+
Airbrake::Rack::ContextFilter,
|
93
|
+
Airbrake::Rack::UserFilter,
|
94
|
+
Airbrake::Rack::SessionFilter,
|
95
|
+
Airbrake::Rack::HttpParamsFilter,
|
96
|
+
Airbrake::Rack::HttpHeadersFilter,
|
97
|
+
Airbrake::Rack::RouteFilter
|
98
|
+
].each do |filter|
|
99
|
+
Airbrake.add_filter(filter.new)
|
100
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# A filter that appends Rack request body to the notice.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# # Read and append up to 512 bytes from Rack request's body.
|
7
|
+
# Airbrake.add_filter(Airbrake::Rack::RequestBodyFilter.new(512))
|
8
|
+
#
|
9
|
+
# @since v5.7.0
|
10
|
+
# @note This filter is *not* used by default.
|
11
|
+
class RequestBodyFilter
|
12
|
+
# @return [Integer]
|
13
|
+
attr_reader :weight
|
14
|
+
|
15
|
+
# @param [Integer] length The maximum number of bytes to read
|
16
|
+
def initialize(length = 4096)
|
17
|
+
@length = length
|
18
|
+
@weight = 95
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see Airbrake::FilterChain#refine
|
22
|
+
def call(notice)
|
23
|
+
return unless (request = notice.stash[:rack_request])
|
24
|
+
return unless request.body
|
25
|
+
|
26
|
+
notice[:environment][:body] = request.body.read(@length)
|
27
|
+
request.body.rewind
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# RequestStore is a thin (and limited) wrapper around *Thread.current* that
|
4
|
+
# allows writing and reading thread-local variables under the +:airbrake+
|
5
|
+
# key.
|
6
|
+
# @api private
|
7
|
+
# @since v8.1.3
|
8
|
+
module RequestStore
|
9
|
+
class << self
|
10
|
+
# @return [Hash] a hash for all request-related data
|
11
|
+
def store
|
12
|
+
Thread.current[:airbrake] ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [void]
|
16
|
+
def []=(key, value)
|
17
|
+
store[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Object]
|
21
|
+
def [](key)
|
22
|
+
store[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [void]
|
26
|
+
def clear
|
27
|
+
Thread.current[:airbrake] = {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# Adds route slugs to context/route.
|
4
|
+
# @since v7.5.0
|
5
|
+
class RouteFilter
|
6
|
+
attr_reader :weight
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@weight = 100
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(notice)
|
13
|
+
return unless (request = notice.stash[:rack_request])
|
14
|
+
|
15
|
+
notice[:context][:route] =
|
16
|
+
if action_dispatch_request?(request)
|
17
|
+
rails_route(request)
|
18
|
+
elsif sinatra_request?(request)
|
19
|
+
sinatra_route(request)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def rails_route(request)
|
26
|
+
::Rails.application.routes.router.recognize(request) do |route, _parameters|
|
27
|
+
# Rails can recognize multiple routes for the given request. For
|
28
|
+
# example, if we visit /users/2/edit, then Rails sees these routes:
|
29
|
+
# * "/users/:id/edit(.:format)"
|
30
|
+
# * "/"
|
31
|
+
#
|
32
|
+
# We return the first route as, what it seems, the most optimal
|
33
|
+
# approach.
|
34
|
+
return route.path.spec.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def sinatra_route(request)
|
39
|
+
return unless (route = request.env['sinatra.route'])
|
40
|
+
route.split(' ').drop(1).join(' ')
|
41
|
+
end
|
42
|
+
|
43
|
+
def action_dispatch_request?(request)
|
44
|
+
defined?(ActionDispatch::Request) &&
|
45
|
+
request.instance_of?(ActionDispatch::Request)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sinatra_request?(request)
|
49
|
+
defined?(Sinatra::Request) && request.instance_of?(Sinatra::Request)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# Adds HTTP session.
|
4
|
+
#
|
5
|
+
# @since v5.7.0
|
6
|
+
class SessionFilter
|
7
|
+
# @return [Integer]
|
8
|
+
attr_reader :weight
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@weight = 96
|
12
|
+
end
|
13
|
+
|
14
|
+
# @see Airbrake::FilterChain#refine
|
15
|
+
def call(notice)
|
16
|
+
return unless (request = notice.stash[:rack_request])
|
17
|
+
|
18
|
+
session = request.session
|
19
|
+
notice[:session] = session if session
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# Represents an authenticated user, which can be converted to Airbrake's
|
4
|
+
# payload format. Supports Warden and Omniauth authentication frameworks.
|
5
|
+
class User
|
6
|
+
# Finds the user in the Rack environment and creates a new user wrapper.
|
7
|
+
#
|
8
|
+
# @param [Hash{String=>Object}] rack_env The Rack environment
|
9
|
+
# @return [Airbrake::Rack::User, nil]
|
10
|
+
def self.extract(rack_env)
|
11
|
+
# Warden support (including Devise).
|
12
|
+
if (warden = rack_env['warden'])
|
13
|
+
user = warden.user(run_callbacks: false)
|
14
|
+
# Early return to prevent unwanted possible authentication via
|
15
|
+
# calling the `current_user` method later.
|
16
|
+
# See: https://github.com/airbrake/airbrake/issues/641
|
17
|
+
return user ? new(user) : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Fallback mode (OmniAuth support included). Works only for Rails.
|
21
|
+
user = try_current_user(rack_env)
|
22
|
+
new(user) if user
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.try_current_user(rack_env)
|
26
|
+
controller = rack_env['action_controller.instance']
|
27
|
+
return unless controller.respond_to?(:current_user, true)
|
28
|
+
return unless [-1, 0].include?(controller.method(:current_user).arity)
|
29
|
+
begin
|
30
|
+
controller.__send__(:current_user)
|
31
|
+
rescue Exception => _e # rubocop:disable Lint/RescueException
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
private_class_method :try_current_user
|
36
|
+
|
37
|
+
def initialize(user)
|
38
|
+
@user = user
|
39
|
+
end
|
40
|
+
|
41
|
+
def as_json
|
42
|
+
user = {}
|
43
|
+
|
44
|
+
user[:id] = try_to_get(:id)
|
45
|
+
user[:name] = full_name
|
46
|
+
user[:username] = try_to_get(:username)
|
47
|
+
user[:email] = try_to_get(:email)
|
48
|
+
|
49
|
+
user = user.delete_if { |_key, val| val.nil? }
|
50
|
+
user.empty? ? user : { user: user }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def try_to_get(key)
|
56
|
+
return unless @user.respond_to?(key)
|
57
|
+
# try methods with no arguments or with variable number of arguments,
|
58
|
+
# where none of them are required
|
59
|
+
return unless @user.method(key).arity.between?(-1, 0)
|
60
|
+
String(@user.__send__(key))
|
61
|
+
end
|
62
|
+
|
63
|
+
def full_name
|
64
|
+
# Try to get first and last names. If that fails, try to get just 'name'.
|
65
|
+
name = [try_to_get(:first_name), try_to_get(:last_name)].compact.join(' ')
|
66
|
+
name.empty? ? try_to_get(:name) : name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rack
|
3
|
+
# Adds current user information.
|
4
|
+
#
|
5
|
+
# @since v8.0.1
|
6
|
+
class UserFilter
|
7
|
+
# @return [Integer]
|
8
|
+
attr_reader :weight
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@weight = 99
|
12
|
+
end
|
13
|
+
|
14
|
+
# @see Airbrake::FilterChain#refine
|
15
|
+
def call(notice)
|
16
|
+
return unless (request = notice.stash[:rack_request])
|
17
|
+
|
18
|
+
user = Airbrake::Rack::User.extract(request.env)
|
19
|
+
notice[:context].merge!(user.as_json) if user
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'airbrake/rails/railtie'
|
2
|
+
|
3
|
+
module Airbrake
|
4
|
+
# Rails namespace holds all Rails-related functionality.
|
5
|
+
module Rails
|
6
|
+
def self.logger
|
7
|
+
if ENV['RAILS_LOG_TO_STDOUT'].present?
|
8
|
+
Logger.new(STDOUT, level: ::Rails.logger.level)
|
9
|
+
else
|
10
|
+
Logger.new(
|
11
|
+
::Rails.root.join('log', 'airbrake.log'),
|
12
|
+
|
13
|
+
# Rails.logger is not set in some Rake tasks such as
|
14
|
+
# 'airbrake:deploy'. In this case we use a sensible fallback.
|
15
|
+
level: (::Rails.logger ? ::Rails.logger.level : Logger::ERROR)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if defined?(ActionController::Metal)
|
23
|
+
require 'airbrake/rails/action_controller'
|
24
|
+
module ActionController
|
25
|
+
# Adds support for Rails API/Metal for Rails < 5. Rails 5+ uses standard
|
26
|
+
# hooks.
|
27
|
+
# @see https://github.com/airbrake/airbrake/issues/821
|
28
|
+
class Metal
|
29
|
+
include Airbrake::Rails::ActionController
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'airbrake/rails/action_cable/notify_callback'
|
2
|
+
|
3
|
+
%i[subscribe unsubscribe].each do |callback_name|
|
4
|
+
ActionCable::Channel::Base.set_callback(
|
5
|
+
callback_name, :around, prepend: true
|
6
|
+
) do |channel, inner|
|
7
|
+
Airbrake::Rails::ActionCable::NotifyCallback.call(channel, inner)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ActionCable
|
12
|
+
module Channel
|
13
|
+
# @since v8.3.0
|
14
|
+
# @api private
|
15
|
+
# @see https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/base.rb
|
16
|
+
class Base
|
17
|
+
alias perform_action_without_airbrake perform_action
|
18
|
+
|
19
|
+
def perform_action(*args, &block)
|
20
|
+
perform_action_without_airbrake(*args, &block)
|
21
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
22
|
+
Airbrake.notify(ex) do |notice|
|
23
|
+
notice.stash[:action_cable_connection] = connection
|
24
|
+
notice[:context][:component] = self.class
|
25
|
+
notice[:context][:action] = args.first['action']
|
26
|
+
notice[:params].merge!(args.first)
|
27
|
+
end
|
28
|
+
|
29
|
+
raise ex
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rails
|
3
|
+
module ActionCable
|
4
|
+
# @since v8.3.0
|
5
|
+
# @api private
|
6
|
+
class NotifyCallback
|
7
|
+
def self.call(channel, block)
|
8
|
+
block.call
|
9
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
10
|
+
notice = Airbrake.build_notice(ex)
|
11
|
+
notice[:context][:component] = 'action_cable'
|
12
|
+
notice[:context][:action] = channel.channel_name
|
13
|
+
Airbrake.notify(notice)
|
14
|
+
|
15
|
+
raise ex
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Airbrake
|
2
|
+
module Rails
|
3
|
+
# Contains helper methods that can be used inside Rails controllers to send
|
4
|
+
# notices to Airbrake. The main benefit of using them instead of the direct
|
5
|
+
# API is that they automatically add information from the Rack environment
|
6
|
+
# to notices.
|
7
|
+
module ActionController
|
8
|
+
private
|
9
|
+
|
10
|
+
# A helper method for sending notices to Airbrake *asynchronously*.
|
11
|
+
# Attaches information from the Rack env.
|
12
|
+
# @see Airbrake#notify, #notify_airbrake_sync
|
13
|
+
def notify_airbrake(exception, params = {}, &block)
|
14
|
+
return unless (notice = build_notice(exception, params))
|
15
|
+
Airbrake.notify(notice, params, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# A helper method for sending notices to Airbrake *synchronously*.
|
19
|
+
# Attaches information from the Rack env.
|
20
|
+
# @see Airbrake#notify_sync, #notify_airbrake
|
21
|
+
def notify_airbrake_sync(exception, params = {}, &block)
|
22
|
+
return unless (notice = build_notice(exception, params))
|
23
|
+
Airbrake.notify_sync(notice, params, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Exception] exception
|
27
|
+
# @return [Airbrake::Notice] the notice with information from the Rack env
|
28
|
+
def build_notice(exception, params = {})
|
29
|
+
return unless (notice = Airbrake.build_notice(exception, params))
|
30
|
+
notice.stash[:rack_request] = request
|
31
|
+
notice
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|