airbrake 9.4.0 → 11.0.1

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 +5 -5
  2. data/lib/airbrake.rb +2 -0
  3. data/lib/airbrake/capistrano.rb +2 -0
  4. data/lib/airbrake/capistrano/capistrano2.rb +2 -0
  5. data/lib/airbrake/capistrano/capistrano3.rb +3 -1
  6. data/lib/airbrake/delayed_job.rb +24 -6
  7. data/lib/airbrake/logger.rb +2 -0
  8. data/lib/airbrake/rack.rb +4 -0
  9. data/lib/airbrake/rack/context_filter.rb +9 -2
  10. data/lib/airbrake/rack/http_headers_filter.rb +7 -5
  11. data/lib/airbrake/rack/http_params_filter.rb +2 -0
  12. data/lib/airbrake/rack/instrumentable.rb +112 -4
  13. data/lib/airbrake/rack/middleware.rb +3 -1
  14. data/lib/airbrake/rack/request_body_filter.rb +2 -0
  15. data/lib/airbrake/rack/request_store.rb +2 -0
  16. data/lib/airbrake/rack/route_filter.rb +8 -10
  17. data/lib/airbrake/rack/session_filter.rb +2 -0
  18. data/lib/airbrake/rack/user.rb +9 -1
  19. data/lib/airbrake/rack/user_filter.rb +2 -0
  20. data/lib/airbrake/rails.rb +14 -135
  21. data/lib/airbrake/rails/action_cable.rb +2 -0
  22. data/lib/airbrake/rails/action_cable/notify_callback.rb +2 -0
  23. data/lib/airbrake/rails/action_controller.rb +9 -4
  24. data/lib/airbrake/rails/action_controller_notify_subscriber.rb +6 -2
  25. data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +6 -1
  26. data/lib/airbrake/rails/action_controller_route_subscriber.rb +8 -15
  27. data/lib/airbrake/rails/active_job.rb +25 -8
  28. data/lib/airbrake/rails/active_record.rb +2 -0
  29. data/lib/airbrake/rails/active_record_subscriber.rb +7 -3
  30. data/lib/airbrake/rails/app.rb +64 -19
  31. data/lib/airbrake/rails/backtrace_cleaner.rb +13 -0
  32. data/lib/airbrake/rails/curb.rb +19 -22
  33. data/lib/airbrake/rails/event.rb +13 -7
  34. data/lib/airbrake/rails/excon_subscriber.rb +4 -0
  35. data/lib/airbrake/rails/http.rb +2 -0
  36. data/lib/airbrake/rails/http_client.rb +12 -6
  37. data/lib/airbrake/rails/net_http.rb +14 -6
  38. data/lib/airbrake/rails/railtie.rb +151 -0
  39. data/lib/airbrake/rails/typhoeus.rb +11 -7
  40. data/lib/airbrake/rake.rb +3 -1
  41. data/lib/airbrake/rake/tasks.rb +7 -5
  42. data/lib/airbrake/resque.rb +32 -0
  43. data/lib/airbrake/shoryuken.rb +17 -3
  44. data/lib/airbrake/sidekiq.rb +22 -14
  45. data/lib/airbrake/sidekiq/retryable_jobs_filter.rb +2 -0
  46. data/lib/airbrake/sneakers.rb +39 -1
  47. data/lib/airbrake/version.rb +3 -1
  48. data/lib/generators/airbrake_generator.rb +2 -0
  49. data/lib/generators/airbrake_initializer.rb.erb +6 -12
  50. metadata +62 -76
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/action_cable/notify_callback'
2
4
 
3
5
  %i[subscribe unsubscribe].each do |callback_name|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rails
3
5
  module ActionCable
@@ -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
- Airbrake.notify(notice, params)
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
- Airbrake.notify_sync(notice, params)
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
- start_time: event.time,
22
- end_time: Time.new
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
- start_time: event.time
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 = find_route(event.params)
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
  module Airbrake
2
4
  module Rails
3
5
  # Rails <4.2 has a bug with regard to swallowing exceptions in the
@@ -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
- start_time: event.time,
26
- end_time: event.end
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
@@ -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 and Rails::Engine.
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, :controller, :action)
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
- private
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
- def app_routes
23
- ::Rails.application.routes.routes.routes
24
- end
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
- def engine_routes
27
- ::Rails::Engine.subclasses.flat_map do |engine|
28
- engine.routes.routes.routes
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
@@ -1,35 +1,32 @@
1
- module Curl
2
- # Monkey-patch to measure request timing.
3
- class Easy
4
- alias http_without_airbrake http
1
+ # frozen_string_literal: true
5
2
 
6
- def http(verb)
7
- Airbrake::Rack.capture_timing(:http) do
8
- http_without_airbrake(verb)
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
- alias perform_without_airbrake perform
13
-
14
- def perform(&block)
15
- Airbrake::Rack.capture_timing(:http) do
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
- http_without_airbrake(urls_with_config, multi_options, &block)
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)
@@ -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 = "*/*".freeze
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
- 0
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