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.
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