airbrake 9.5.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 +4 -4
  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 +4 -0
  19. data/lib/airbrake/rack/user_filter.rb +2 -0
  20. data/lib/airbrake/rails.rb +8 -8
  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 +5 -0
  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 -21
  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 +60 -25
  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 +4 -6
  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 +55 -45
  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 +5 -3
  50. metadata +61 -90
@@ -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
@@ -12,6 +14,7 @@ module Airbrake
12
14
  # @see Airbrake#notify, #notify_airbrake_sync
13
15
  def notify_airbrake(exception, params = {}, &block)
14
16
  return unless (notice = build_notice(exception, params))
17
+
15
18
  Airbrake.notify(notice, params, &block)
16
19
  end
17
20
 
@@ -20,6 +23,7 @@ module Airbrake
20
23
  # @see Airbrake#notify_sync, #notify_airbrake
21
24
  def notify_airbrake_sync(exception, params = {}, &block)
22
25
  return unless (notice = build_notice(exception, params))
26
+
23
27
  Airbrake.notify_sync(notice, params, &block)
24
28
  end
25
29
 
@@ -27,6 +31,7 @@ module Airbrake
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,46 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/event'
2
4
  require 'airbrake/rails/app'
3
5
 
4
6
  module Airbrake
5
7
  module Rails
6
- # @return [String]
7
- CONTROLLER_KEY = 'controller'.freeze
8
-
9
- # @return [String]
10
- ACTION_KEY = 'action'.freeze
11
-
12
8
  # ActionControllerRouteSubscriber sends route stat information, including
13
9
  # performance data.
14
10
  #
15
11
  # @since v8.0.0
16
12
  class ActionControllerRouteSubscriber
17
- def initialize
18
- @app = Airbrake::Rails::App.new
19
- end
20
-
21
13
  def call(*args)
14
+ return unless Airbrake::Config.instance.performance_stats
15
+
22
16
  # We don't track routeless events.
23
17
  return unless (routes = Airbrake::Rack::RequestStore[:routes])
24
18
 
25
19
  event = Airbrake::Rails::Event.new(*args)
26
- route = find_route(event.params)
20
+ route = Airbrake::Rails::App.recognize_route(
21
+ Airbrake::Rack::RequestStore[:request],
22
+ )
27
23
  return unless route
28
24
 
29
25
  routes[route.path] = {
30
26
  method: event.method,
31
27
  response_type: event.response_type,
32
- groups: {}
28
+ groups: {},
33
29
  }
34
30
  end
35
-
36
- private
37
-
38
- def find_route(params)
39
- @app.routes.find do |route|
40
- route.controller == params[CONTROLLER_KEY] &&
41
- route.action == params[ACTION_KEY]
42
- end
43
- end
44
31
  end
45
32
  end
46
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,43 +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)
10
+ Route = Struct.new(:path)
9
11
 
10
- def routes
11
- @routes ||= app_routes.merge(engine_routes).flat_map do |(engine_name, routes)|
12
- routes.map { |rails_route| build_route(engine_name, rails_route) }
13
- end
14
- end
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
15
20
 
16
- private
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']
17
26
 
18
- def app_routes
19
- # Engine name is nil because this is default (non-engine) routes.
20
- { nil => ::Rails.application.routes.routes.routes }
21
- end
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
22
39
 
23
- def engine_routes
24
- ::Rails::Engine.subclasses.flat_map.with_object({}) do |engine, hash|
25
- next if (eng_routes = engine.routes.routes.routes).none?
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?
26
49
 
27
- hash[engine.engine_name] = eng_routes
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
28
66
  end
29
- end
30
67
 
31
- def build_route(engine_name, rails_route)
32
- engine_prefix = engine_name
33
- engine_prefix += '#' if engine_prefix
68
+ nil
69
+ end
70
+ # rubocop:enable Metrics/AbcSize
34
71
 
35
- Route.new(
36
- "#{engine_prefix}#{rails_route.path.spec}",
37
- rails_route.defaults[:controller],
38
- rails_route.defaults[:action]
39
- )
72
+ def self.engines
73
+ @engines ||= [*::Rails::Engine.subclasses, ::Rails.application]
40
74
  end
75
+ private_class_method :engines
41
76
  end
42
77
  end
43
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,7 @@ 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 = "*/*"
10
12
 
11
13
  include Airbrake::Loggable
12
14
 
@@ -43,10 +45,6 @@ module Airbrake
43
45
  @event.time
44
46
  end
45
47
 
46
- def end
47
- @event.end
48
- end
49
-
50
48
  def groups
51
49
  groups = {}
52
50
  groups[:db] = db_runtime if db_runtime > 0
@@ -59,7 +57,7 @@ module Airbrake
59
57
 
60
58
  if @event.payload[:exception]
61
59
  status = ActionDispatch::ExceptionWrapper.status_code_for_exception(
62
- @event.payload[:exception].first
60
+ @event.payload[:exception].first,
63
61
  )
64
62
  status = 500 if status == 0
65
63