airbrake 9.5.0 → 11.0.1

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