airbrake 9.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake.rb +30 -0
  3. data/lib/airbrake/capistrano.rb +6 -0
  4. data/lib/airbrake/capistrano/capistrano2.rb +38 -0
  5. data/lib/airbrake/capistrano/capistrano3.rb +21 -0
  6. data/lib/airbrake/delayed_job.rb +48 -0
  7. data/lib/airbrake/logger.rb +101 -0
  8. data/lib/airbrake/rack.rb +35 -0
  9. data/lib/airbrake/rack/context_filter.rb +58 -0
  10. data/lib/airbrake/rack/http_headers_filter.rb +42 -0
  11. data/lib/airbrake/rack/http_params_filter.rb +25 -0
  12. data/lib/airbrake/rack/instrumentable.rb +28 -0
  13. data/lib/airbrake/rack/middleware.rb +100 -0
  14. data/lib/airbrake/rack/request_body_filter.rb +31 -0
  15. data/lib/airbrake/rack/request_store.rb +32 -0
  16. data/lib/airbrake/rack/route_filter.rb +53 -0
  17. data/lib/airbrake/rack/session_filter.rb +23 -0
  18. data/lib/airbrake/rack/user.rb +70 -0
  19. data/lib/airbrake/rack/user_filter.rb +23 -0
  20. data/lib/airbrake/rails.rb +32 -0
  21. data/lib/airbrake/rails/action_cable.rb +33 -0
  22. data/lib/airbrake/rails/action_cable/notify_callback.rb +20 -0
  23. data/lib/airbrake/rails/action_controller.rb +35 -0
  24. data/lib/airbrake/rails/action_controller_notify_subscriber.rb +28 -0
  25. data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +46 -0
  26. data/lib/airbrake/rails/action_controller_route_subscriber.rb +46 -0
  27. data/lib/airbrake/rails/active_job.rb +33 -0
  28. data/lib/airbrake/rails/active_record.rb +34 -0
  29. data/lib/airbrake/rails/active_record_subscriber.rb +42 -0
  30. data/lib/airbrake/rails/app.rb +43 -0
  31. data/lib/airbrake/rails/backtrace_cleaner.rb +10 -0
  32. data/lib/airbrake/rails/curb.rb +35 -0
  33. data/lib/airbrake/rails/event.rb +83 -0
  34. data/lib/airbrake/rails/excon_subscriber.rb +21 -0
  35. data/lib/airbrake/rails/http.rb +12 -0
  36. data/lib/airbrake/rails/http_client.rb +10 -0
  37. data/lib/airbrake/rails/net_http.rb +10 -0
  38. data/lib/airbrake/rails/railtie.rb +141 -0
  39. data/lib/airbrake/rails/typhoeus.rb +12 -0
  40. data/lib/airbrake/rake.rb +63 -0
  41. data/lib/airbrake/rake/tasks.rb +110 -0
  42. data/lib/airbrake/resque.rb +29 -0
  43. data/lib/airbrake/shoryuken.rb +40 -0
  44. data/lib/airbrake/sidekiq.rb +47 -0
  45. data/lib/airbrake/sidekiq/retryable_jobs_filter.rb +51 -0
  46. data/lib/airbrake/sneakers.rb +34 -0
  47. data/lib/airbrake/version.rb +5 -0
  48. data/lib/generators/airbrake_generator.rb +23 -0
  49. data/lib/generators/airbrake_initializer.rb.erb +78 -0
  50. 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