airbrake 9.4.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|