airbrake 10.0.2 → 13.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake/capistrano/capistrano2.rb +1 -1
  3. data/lib/airbrake/capistrano/capistrano3.rb +1 -1
  4. data/lib/airbrake/delayed_job.rb +28 -30
  5. data/lib/airbrake/logger.rb +3 -1
  6. data/lib/airbrake/rack/context_filter.rb +10 -6
  7. data/lib/airbrake/rack/http_headers_filter.rb +4 -4
  8. data/lib/airbrake/rack/instrumentable.rb +9 -5
  9. data/lib/airbrake/rack/route_filter.rb +3 -1
  10. data/lib/airbrake/rack/user.rb +2 -0
  11. data/lib/airbrake/rack.rb +2 -0
  12. data/lib/airbrake/rails/action_cable.rb +19 -17
  13. data/lib/airbrake/rails/action_controller.rb +3 -0
  14. data/lib/airbrake/rails/action_controller_notify_subscriber.rb +2 -0
  15. data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +2 -0
  16. data/lib/airbrake/rails/action_controller_route_subscriber.rb +2 -0
  17. data/lib/airbrake/rails/active_job.rb +2 -2
  18. data/lib/airbrake/rails/active_record_subscriber.rb +2 -0
  19. data/lib/airbrake/rails/app.rb +22 -0
  20. data/lib/airbrake/rails/backtrace_cleaner.rb +11 -0
  21. data/lib/airbrake/rails/curb.rb +18 -23
  22. data/lib/airbrake/rails/event.rb +14 -2
  23. data/lib/airbrake/rails/excon_subscriber.rb +2 -0
  24. data/lib/airbrake/rails/http.rb +12 -8
  25. data/lib/airbrake/rails/http_client.rb +11 -7
  26. data/lib/airbrake/rails/railtie.rb +7 -99
  27. data/lib/airbrake/rails/railties/action_controller_tie.rb +90 -0
  28. data/lib/airbrake/rails/railties/active_record_tie.rb +74 -0
  29. data/lib/airbrake/rails/railties/middleware_tie.rb +62 -0
  30. data/lib/airbrake/rails/typhoeus.rb +10 -8
  31. data/lib/airbrake/rails.rb +6 -8
  32. data/lib/airbrake/rake/tasks.rb +10 -10
  33. data/lib/airbrake/rake.rb +47 -46
  34. data/lib/airbrake/resque.rb +26 -25
  35. data/lib/airbrake/shoryuken.rb +2 -4
  36. data/lib/airbrake/sidekiq/retryable_jobs_filter.rb +14 -8
  37. data/lib/airbrake/sidekiq.rb +5 -6
  38. data/lib/airbrake/sneakers.rb +28 -27
  39. data/lib/airbrake/version.rb +1 -1
  40. data/lib/generators/airbrake_generator.rb +3 -6
  41. data/lib/generators/airbrake_initializer.rb.erb +65 -65
  42. metadata +52 -138
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5138c1675426cc908ae8b1857898ddc04c135fda23dfe21db19e6b1bf0b36faf
4
- data.tar.gz: '085abd9125a9a615935ac2b1171cdfd74205bde892708e073bfbda44103c72ca'
3
+ metadata.gz: 4f91f4d04951002ed983c872b4d6042c1cc4c01ae609d3969b4b7e9b344bdf12
4
+ data.tar.gz: 58ee6fd62567a65f7df5178f6d0d9e6bd9cd58e9debbf62d9d75fa741b20b6d3
5
5
  SHA512:
6
- metadata.gz: 39bc3cfe3510b0db2adece334666b4e753298fd4d09f4d7d445086c0512aa0436ec8c5b38a85933fae822efeea155d7d2bea7e25866e169878b6f1b330fb4d4d
7
- data.tar.gz: 9c189959ccdfa6c30752462dffb49e8925df101f18597180ab6a31e2320a620431bcbcf6d66c83838d1acfd9be1b6c98623253e60cea77e8881fc84e5996aa14
6
+ metadata.gz: 6b464a25e936fafa269f35bb5c19ed6134ba65f4184f9d76e5f5a349370e1277dc71418393c21a8427dcecc572aabaa941a5ae41dbbfa1547fa280d1416fc630
7
+ data.tar.gz: a78fdeaba2521c51e47b4f9bbee5674b1d3228e25345e7d20cac5c3186c856c6c860a5daee82adc32f8001bce3b6422be50c31dbba32912f43dd535ba1dc965b
@@ -21,7 +21,7 @@ module Airbrake
21
21
  RAILS_ENV=#{fetch(:rails_env, nil)} \
22
22
 
23
23
  bundle exec rake airbrake:deploy \
24
- USERNAME=#{Shellwords.shellescape(ENV['USER'] || ENV['USERNAME'])} \
24
+ USERNAME=#{Shellwords.shellescape(ENV.fetch('USER', nil) || ENV.fetch('USERNAME', nil))} \
25
25
  ENVIRONMENT=#{fetch(:airbrake_env, fetch(:rails_env, 'production'))} \
26
26
  REVISION=#{current_revision.strip} \
27
27
  REPOSITORY=#{repository} \
@@ -13,7 +13,7 @@ namespace :airbrake do
13
13
  REVISION=#{fetch(:current_revision)} \
14
14
  REPOSITORY=#{fetch(:repo_url)} \
15
15
  VERSION=#{fetch(:app_version)}
16
- CMD
16
+ CMD
17
17
 
18
18
  info 'Notified Airbrake of the deploy'
19
19
  end
@@ -7,41 +7,39 @@ module Delayed
7
7
  class Airbrake < ::Delayed::Plugin
8
8
  callbacks do |lifecycle|
9
9
  lifecycle.around(:invoke_job) do |job, *args, &block|
10
- begin
11
- timing = ::Airbrake::Benchmark.measure do
12
- # Forward the call to the next callback in the callback chain
13
- block.call(job, *args)
14
- end
15
- rescue Exception => exception # rubocop:disable Lint/RescueException
16
- params = job.as_json
10
+ timing = ::Airbrake::Benchmark.measure do
11
+ # Forward the call to the next callback in the callback chain
12
+ block.call(job, *args)
13
+ end
14
+ rescue Exception => exception # rubocop:disable Lint/RescueException
15
+ params = job.as_json
17
16
 
18
- # If DelayedJob is used through ActiveJob, it contains extra info.
19
- if job.payload_object.respond_to?(:job_data)
20
- params[:active_job] = job.payload_object.job_data
21
- job_class = job.payload_object.job_data['job_class']
22
- end
17
+ # If DelayedJob is used through ActiveJob, it contains extra info.
18
+ if job.payload_object.respond_to?(:job_data)
19
+ params[:active_job] = job.payload_object.job_data
20
+ job_class = job.payload_object.job_data['job_class']
21
+ end
23
22
 
24
- action = job_class || job.payload_object.class.name
23
+ action = job_class || job.payload_object.class.name
25
24
 
26
- ::Airbrake.notify(exception, params) do |notice|
27
- notice[:context][:component] = 'delayed_job'
28
- notice[:context][:action] = action
29
- end
25
+ ::Airbrake.notify(exception, params) do |notice|
26
+ notice[:context][:component] = 'delayed_job'
27
+ notice[:context][:action] = action
28
+ end
30
29
 
31
- ::Airbrake.notify_queue_sync(
32
- queue: action,
33
- error_count: 1,
34
- timing: 0.01,
35
- )
30
+ ::Airbrake.notify_queue(
31
+ queue: action,
32
+ error_count: 1,
33
+ timing: 0.01,
34
+ )
36
35
 
37
- raise exception
38
- else
39
- ::Airbrake.notify_queue_sync(
40
- queue: job_class || job.payload_object.class.name,
41
- error_count: 0,
42
- timing: timing,
43
- )
44
- end
36
+ raise exception
37
+ else
38
+ ::Airbrake.notify_queue(
39
+ queue: job_class || job.payload_object.class.name,
40
+ error_count: 0,
41
+ timing: timing,
42
+ )
45
43
  end
46
44
  end
47
45
  end
@@ -9,7 +9,7 @@ module Airbrake
9
9
  #
10
10
  # @example
11
11
  # # Create a logger like you normally do and decorate it.
12
- # logger = Airbrake::AirbrakeLogger.new(Logger.new(STDOUT))
12
+ # logger = Airbrake::AirbrakeLogger.new(Logger.new($stdout))
13
13
  #
14
14
  # # Just use the logger like you normally do.
15
15
  # logger.fatal('oops')
@@ -24,6 +24,8 @@ module Airbrake
24
24
  attr_reader :airbrake_level
25
25
 
26
26
  def initialize(logger)
27
+ super
28
+
27
29
  __setobj__(logger)
28
30
  @airbrake_notifier = Airbrake
29
31
  self.level = logger.level
@@ -20,14 +20,9 @@ module Airbrake
20
20
  context = notice[:context]
21
21
 
22
22
  context[:url] = request.url
23
- context[:userAddr] =
24
- if request.respond_to?(:remote_ip)
25
- request.remote_ip
26
- else
27
- request.ip
28
- end
29
23
  context[:userAgent] = request.user_agent
30
24
 
25
+ add_ip(context, request)
31
26
  add_framework_version(context)
32
27
 
33
28
  controller = request.env['action_controller.instance']
@@ -60,6 +55,15 @@ module Airbrake
60
55
  }
61
56
  end
62
57
  end
58
+
59
+ def add_ip(context, request)
60
+ context[:userAddr] =
61
+ if request.respond_to?(:remote_ip)
62
+ request.remote_ip
63
+ else
64
+ request.ip
65
+ end
66
+ end
63
67
  end
64
68
  end
65
69
  end
@@ -8,10 +8,10 @@ module Airbrake
8
8
  class HttpHeadersFilter
9
9
  # @return [Array<String>] the prefixes of the majority of HTTP headers in
10
10
  # Rack (some prefixes match the header names for simplicity)
11
- HTTP_HEADER_PREFIXES = [
12
- 'HTTP_'.freeze,
13
- 'CONTENT_TYPE'.freeze,
14
- 'CONTENT_LENGTH'.freeze,
11
+ HTTP_HEADER_PREFIXES = %w[
12
+ HTTP_
13
+ CONTENT_TYPE
14
+ CONTENT_LENGTH
15
15
  ].freeze
16
16
 
17
17
  # @return [Integer]
@@ -57,6 +57,7 @@ module Airbrake
57
57
  klass.module_exec do
58
58
  mod = __airbrake_capture_timing_module__
59
59
  mod.module_exec do
60
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
60
61
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
61
62
  def #{method_name}(#{args})
62
63
  Airbrake::Rack.capture_timing(#{label.to_s.inspect}) do
@@ -65,6 +66,7 @@ module Airbrake
65
66
  end
66
67
  #{visibility} :#{method_name}
67
68
  RUBY
69
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
68
70
  end
69
71
  prepend mod
70
72
  end
@@ -83,6 +85,7 @@ module Airbrake
83
85
  klass.module_exec do
84
86
  alias_method wrapped_method_name, method_name
85
87
  remove_method method_name if needs_removal
88
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
86
89
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
87
90
  def #{method_name}(#{args})
88
91
  Airbrake::Rack.capture_timing(#{label.to_s.inspect}) do
@@ -91,6 +94,7 @@ module Airbrake
91
94
  end
92
95
  #{visibility} :#{method_name}
93
96
  RUBY
97
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
94
98
  end
95
99
  end
96
100
 
@@ -98,11 +102,11 @@ module Airbrake
98
102
  def self.method_visibility(klass, method_name)
99
103
  klass.module_exec do
100
104
  if protected_method_defined?(method_name)
101
- "protected".freeze
105
+ "protected"
102
106
  elsif private_method_defined?(method_name)
103
- "private".freeze
107
+ "private"
104
108
  else
105
- "public".freeze
109
+ "public"
106
110
  end
107
111
  end
108
112
  end
@@ -111,11 +115,11 @@ module Airbrake
111
115
  # A method instead of a constant so it isn't accessible in the target.
112
116
  if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
113
117
  def self.method_signature
114
- "*args, **kw_args, &block".freeze
118
+ "*args, **kw_args, &block"
115
119
  end
116
120
  else
117
121
  def self.method_signature
118
- "*args, &block".freeze
122
+ "*args, &block"
119
123
  end
120
124
  end
121
125
 
@@ -28,12 +28,14 @@ module Airbrake
28
28
 
29
29
  def rails_route(request)
30
30
  return unless (route = Airbrake::Rails::App.recognize_route(request))
31
+
31
32
  route.path
32
33
  end
33
34
 
34
35
  def sinatra_route(request)
35
36
  return unless (route = request.env['sinatra.route'])
36
- route.split(' ').drop(1).join(' ')
37
+
38
+ route.split.drop(1).join(' ')
37
39
  end
38
40
 
39
41
  def action_dispatch_request?(request)
@@ -28,6 +28,7 @@ module Airbrake
28
28
  controller = rack_env['action_controller.instance']
29
29
  return unless controller.respond_to?(:current_user, true)
30
30
  return unless [-1, 0].include?(controller.method(:current_user).arity)
31
+
31
32
  begin
32
33
  controller.__send__(:current_user)
33
34
  rescue Exception => _e # rubocop:disable Lint/RescueException
@@ -59,6 +60,7 @@ module Airbrake
59
60
  # try methods with no arguments or with variable number of arguments,
60
61
  # where none of them are required
61
62
  return unless @user.method(key).arity.between?(-1, 0)
63
+
62
64
  String(@user.__send__(key))
63
65
  end
64
66
 
data/lib/airbrake/rack.rb CHANGED
@@ -18,6 +18,8 @@ module Airbrake
18
18
  # @since v9.2.0
19
19
  # @api public
20
20
  def self.capture_timing(label)
21
+ return yield unless Airbrake::Config.instance.performance_stats
22
+
21
23
  routes = Airbrake::Rack::RequestStore[:routes]
22
24
  if !routes || routes.none?
23
25
  result = yield
@@ -10,26 +10,28 @@ require 'airbrake/rails/action_cable/notify_callback'
10
10
  end
11
11
  end
12
12
 
13
- module ActionCable
14
- module Channel
15
- # @since v8.3.0
16
- # @api private
17
- # @see https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/base.rb
18
- class Base
19
- alias perform_action_without_airbrake perform_action
13
+ module Airbrake
14
+ module ActionCable
15
+ module Channel
16
+ # @since v8.3.0
17
+ # @api private
18
+ # @see https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/base.rb
19
+ module Base
20
+ def perform_action(*args, &block)
21
+ super(*args, &block)
22
+ rescue Exception => ex # rubocop:disable Lint/RescueException
23
+ Airbrake.notify(ex) do |notice|
24
+ notice.stash[:action_cable_connection] = connection
25
+ notice[:context][:component] = self.class
26
+ notice[:context][:action] = args.first['action']
27
+ notice[:params].merge!(args.first)
28
+ end
20
29
 
21
- def perform_action(*args, &block)
22
- perform_action_without_airbrake(*args, &block)
23
- rescue Exception => ex # rubocop:disable Lint/RescueException
24
- Airbrake.notify(ex) do |notice|
25
- notice.stash[:action_cable_connection] = connection
26
- notice[:context][:component] = self.class
27
- notice[:context][:action] = args.first['action']
28
- notice[:params].merge!(args.first)
30
+ raise ex
29
31
  end
30
-
31
- raise ex
32
32
  end
33
33
  end
34
34
  end
35
35
  end
36
+
37
+ ActionCable::Channel::Base.prepend(Airbrake::ActionCable::Channel::Base)
@@ -14,6 +14,7 @@ module Airbrake
14
14
  # @see Airbrake#notify, #notify_airbrake_sync
15
15
  def notify_airbrake(exception, params = {}, &block)
16
16
  return unless (notice = build_notice(exception, params))
17
+
17
18
  Airbrake.notify(notice, params, &block)
18
19
  end
19
20
 
@@ -22,6 +23,7 @@ module Airbrake
22
23
  # @see Airbrake#notify_sync, #notify_airbrake
23
24
  def notify_airbrake_sync(exception, params = {}, &block)
24
25
  return unless (notice = build_notice(exception, params))
26
+
25
27
  Airbrake.notify_sync(notice, params, &block)
26
28
  end
27
29
 
@@ -29,6 +31,7 @@ module Airbrake
29
31
  # @return [Airbrake::Notice] the notice with information from the Rack env
30
32
  def build_notice(exception, params = {})
31
33
  return unless (notice = Airbrake.build_notice(exception, params))
34
+
32
35
  notice.stash[:rack_request] = request
33
36
  notice
34
37
  end
@@ -10,6 +10,8 @@ module Airbrake
10
10
  # @since v8.0.0
11
11
  class ActionControllerNotifySubscriber
12
12
  def call(*args)
13
+ return unless Airbrake::Config.instance.performance_stats
14
+
13
15
  routes = Airbrake::Rack::RequestStore[:routes]
14
16
  return if !routes || routes.none?
15
17
 
@@ -7,6 +7,8 @@ module Airbrake
7
7
  # @since v8.3.0
8
8
  class ActionControllerPerformanceBreakdownSubscriber
9
9
  def call(*args)
10
+ return unless Airbrake::Config.instance.performance_stats
11
+
10
12
  routes = Airbrake::Rack::RequestStore[:routes]
11
13
  return if !routes || routes.none?
12
14
 
@@ -11,6 +11,8 @@ module Airbrake
11
11
  # @since v8.0.0
12
12
  class ActionControllerRouteSubscriber
13
13
  def call(*args)
14
+ return unless Airbrake::Config.instance.performance_stats
15
+
14
16
  # We don't track routeless events.
15
17
  return unless (routes = Airbrake::Rack::RequestStore[:routes])
16
18
 
@@ -22,14 +22,14 @@ module Airbrake
22
22
  block.call
23
23
  end
24
24
  rescue StandardError => exception
25
- Airbrake.notify_queue_sync(
25
+ Airbrake.notify_queue(
26
26
  queue: job.class.name,
27
27
  error_count: 1,
28
28
  timing: 0.01,
29
29
  )
30
30
  raise exception
31
31
  else
32
- Airbrake.notify_queue_sync(
32
+ Airbrake.notify_queue(
33
33
  queue: job.class.name,
34
34
  error_count: 0,
35
35
  timing: timing,
@@ -10,6 +10,8 @@ module Airbrake
10
10
  # @since v8.1.0
11
11
  class ActiveRecordSubscriber
12
12
  def call(*args)
13
+ return unless Airbrake::Config.instance.query_stats
14
+
13
15
  routes = Airbrake::Rack::RequestStore[:routes]
14
16
  return if !routes || routes.none?
15
17
 
@@ -11,12 +11,19 @@ module Airbrake
11
11
 
12
12
  # @param [] request
13
13
  # @return [Airbrake::Rails::App::Route, nil]
14
+ # rubocop:disable Metrics/AbcSize
14
15
  def self.recognize_route(request)
15
16
  # Duplicate `request` because `recognize` *can* strip the request's
16
17
  # `path_info`, which results in broken engine links (when the engine has
17
18
  # an isolated namespace).
18
19
  request_copy = request.dup
19
20
 
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
+
20
27
  # We must search every engine individually to find a concrete route. If
21
28
  # we rely only on the `Rails.application.routes.router`, then the
22
29
  # recognize call would return the root route, neglecting PATH_INFO
@@ -26,6 +33,20 @@ module Airbrake
26
33
  # * `Marketing::Engine` recognizes it as `marketing#/pricing` (correct)
27
34
  engines.each do |engine|
28
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
39
+
40
+ # Skip "catch-all" routes such as:
41
+ # get '*path => 'pages#about'
42
+ #
43
+ # Ideally, we should be using `route.glob?` but in Rails 7+ this
44
+ # call would fail with a `NoMethodError`. This is because in
45
+ # Rails 7+ the AST for the route is not kept in memory anymore.
46
+ #
47
+ # See: https://github.com/rails/rails/pull/43006#discussion_r783895766
48
+ next if route.path.spec.any?(ActionDispatch::Journey::Nodes::Star)
49
+
29
50
  path =
30
51
  if engine == ::Rails.application
31
52
  route.path.spec.to_s
@@ -46,6 +67,7 @@ module Airbrake
46
67
 
47
68
  nil
48
69
  end
70
+ # rubocop:enable Metrics/AbcSize
49
71
 
50
72
  def self.engines
51
73
  @engines ||= [*::Rails::Engine.subclasses, ::Rails.application]
@@ -4,9 +4,20 @@ module Airbrake
4
4
  module Rails
5
5
  # BacktraceCleaner is a wrapper around Rails.backtrace_cleaner.
6
6
  class BacktraceCleaner
7
+ # @return [Regexp]
8
+ AIRBRAKE_FRAME_PATTERN = %r{/airbrake/lib/airbrake/}.freeze
9
+
7
10
  def self.clean(backtrace)
8
11
  ::Rails.backtrace_cleaner.clean(backtrace).first(1)
9
12
  end
10
13
  end
11
14
  end
12
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,37 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Curl
4
- # Monkey-patch to measure request timing.
5
- class Easy
6
- alias http_without_airbrake http
7
-
8
- def http(verb)
9
- Airbrake::Rack.capture_timing(:http) do
10
- 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
11
11
  end
12
- end
13
-
14
- alias perform_without_airbrake perform
15
12
 
16
- def perform(&block)
17
- Airbrake::Rack.capture_timing(:http) do
18
- perform_without_airbrake(&block)
13
+ def perform(&block)
14
+ Airbrake::Rack.capture_timing(:http) do
15
+ super(&block)
16
+ end
19
17
  end
20
18
  end
21
- end
22
- end
23
-
24
- module Curl
25
- # Monkey-patch to measure request timing.
26
- class Multi
27
- class << self
28
- alias http_without_airbrake http
29
19
 
20
+ # Allows measuring request timing.
21
+ module CurlMulti
30
22
  def http(urls_with_config, multi_options = {}, &block)
31
23
  Airbrake::Rack.capture_timing(:http) do
32
- http_without_airbrake(urls_with_config, multi_options, &block)
24
+ super(urls_with_config, multi_options, &block)
33
25
  end
34
26
  end
35
27
  end
36
28
  end
37
29
  end
30
+
31
+ Curl::Easy.prepend(Airbrake::Rails::CurlEasy)
32
+ Curl::Multi.singleton_class.prepend(Airbrake::Rails::CurlMulti)
@@ -8,12 +8,16 @@ module Airbrake
8
8
  # @api private
9
9
  class Event
10
10
  # @see https://github.com/rails/rails/issues/8987
11
- HTML_RESPONSE_WILDCARD = "*/*".freeze
11
+ HTML_RESPONSE_WILDCARD = "*/*"
12
+
13
+ # @return [Integer]
14
+ MILLISECOND = 1000
12
15
 
13
16
  include Airbrake::Loggable
14
17
 
15
18
  def initialize(*args)
16
19
  @event = ActiveSupport::Notifications::Event.new(*args)
20
+ @rails_7_or_greater = ::Rails::VERSION::MAJOR >= 7
17
21
  end
18
22
 
19
23
  def method
@@ -42,7 +46,15 @@ module Airbrake
42
46
  end
43
47
 
44
48
  def time
45
- @event.time
49
+ # On Rails 7+ `ActiveSupport::Notifications::Event#time` returns an
50
+ # instance of Float. It represents monotonic time in milliseconds.
51
+ # Airbrake Ruby expects that the provided time is in seconds. Hence,
52
+ # we need to convert it from milliseconds to seconds. In the
53
+ # versions below Rails 7, time is an instance of Time.
54
+ #
55
+ # Relevant commit:
56
+ # https://github.com/rails/rails/commit/81d0dc90becfe0b8e7f7f26beb66c25d84b8ec7f
57
+ @rails_7_or_greater ? @event.time / MILLISECOND : @event.time
46
58
  end
47
59
 
48
60
  def groups
@@ -8,6 +8,8 @@ module Airbrake
8
8
  # @since v9.2.0
9
9
  class Excon
10
10
  def call(*args)
11
+ return unless Airbrake::Config.instance.performance_stats
12
+
11
13
  routes = Airbrake::Rack::RequestStore[:routes]
12
14
  return if !routes || routes.none?
13
15
 
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module HTTP
4
- # Monkey-patch to measure request timing.
5
- class Client
6
- alias perform_without_airbrake perform
7
-
8
- def perform(request, options)
9
- Airbrake::Rack.capture_timing(:http) do
10
- perform_without_airbrake(request, options)
3
+ module Airbrake
4
+ module Rails
5
+ # Monkey-patch to measure request timing.
6
+ # @api private
7
+ # @since v11.0.2
8
+ module HTTP
9
+ def perform(request, options)
10
+ Airbrake::Rack.capture_timing(:http) do
11
+ super(request, options)
12
+ end
11
13
  end
12
14
  end
13
15
  end
14
16
  end
17
+
18
+ HTTP::Client.prepend(Airbrake::Rails::HTTP)
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Monkey-patch to measure request timing.
4
- class HTTPClient
5
- alias do_get_without_airbrake do_get_block
6
-
7
- def do_get_block(request, proxy, connection, &block)
8
- Airbrake::Rack.capture_timing(:http) do
9
- do_get_without_airbrake(request, proxy, connection, &block)
3
+ module Airbrake
4
+ module Rails
5
+ # Allows measuring request timing.
6
+ module HTTPClient
7
+ def do_get_block(request, proxy, connection, &block)
8
+ Airbrake::Rack.capture_timing(:http) do
9
+ super(request, proxy, connection, &block)
10
+ end
11
+ end
10
12
  end
11
13
  end
12
14
  end
15
+
16
+ HTTPClient.prepend(Airbrake::Rails::HTTPClient)