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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29bdd7916d90d84ac4933c2ce14d9069bd719a371bf822a3b886175841b2005c
4
- data.tar.gz: 94f382b83ce554984a9c4e8162241be85baa78739d928e3c74081cfb17217f1a
3
+ metadata.gz: 4b780328fc01621a0273577655179008c6314c6fe5187e5562f15c5112f296cd
4
+ data.tar.gz: 3036e80e03bbb2fa4159a9167a6971adcda2dcbee789ed3db320c0eaf0408d9b
5
5
  SHA512:
6
- metadata.gz: e7e27d67804da299808bd87f6d2f201ab778ef344ff377283536d5be2082c579df9e54102eb83d48a5cadc78cf434bfcb7bf3c999a816b449f8930e539c96743
7
- data.tar.gz: '028d1865c3e42686c54455164087d401f3dabbd1380b915aec7ca8191e0f777dc26c32930c9118bd160ef1bd6ba63f1fac289a4786f9fc720c98f7a3eadfe2ae'
6
+ metadata.gz: f307768db070afd129623ed312e7f4da4e4040f1bdf33cb9d1e31f2f1ee4278e992eac0e901a6bb257042ff9aff5f651c2a8bd74579606771c56d4040e63c0b1
7
+ data.tar.gz: b6376325c1b352ac7f13a0a0467737fb2a8cb08a12748c10d991265342119bcd6f510271744facb11018312de593857835aceadef557c20bbf1e85a2296e7bd4
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'shellwords'
2
4
  require 'English'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if defined?(Capistrano::VERSION) &&
2
4
  Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
3
5
  require 'airbrake/capistrano/capistrano3'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  # The Capistrano v2 integration.
3
5
  module Capistrano
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :airbrake do
2
4
  desc "Notify Airbrake of the deploy"
3
5
  task :deploy do
@@ -11,7 +13,7 @@ namespace :airbrake do
11
13
  REVISION=#{fetch(:current_revision)} \
12
14
  REPOSITORY=#{fetch(:repo_url)} \
13
15
  VERSION=#{fetch(:app_version)}
14
- CMD
16
+ CMD
15
17
 
16
18
  info 'Notified Airbrake of the deploy'
17
19
  end
@@ -1,14 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Delayed
2
4
  module Plugins
3
5
  # Provides integration with Delayed Job.
4
- # rubocop:disable Lint/RescueException
6
+ # rubocop:disable Metrics/BlockLength
5
7
  class Airbrake < ::Delayed::Plugin
6
8
  callbacks do |lifecycle|
7
9
  lifecycle.around(:invoke_job) do |job, *args, &block|
8
10
  begin
9
- # Forward the call to the next callback in the callback chain
10
- block.call(job, *args)
11
- rescue Exception => exception
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
12
16
  params = job.as_json
13
17
 
14
18
  # If DelayedJob is used through ActiveJob, it contains extra info.
@@ -17,17 +21,31 @@ module Delayed
17
21
  job_class = job.payload_object.job_data['job_class']
18
22
  end
19
23
 
24
+ action = job_class || job.payload_object.class.name
25
+
20
26
  ::Airbrake.notify(exception, params) do |notice|
21
27
  notice[:context][:component] = 'delayed_job'
22
- notice[:context][:action] = job_class || job.payload_object.class.name
28
+ notice[:context][:action] = action
23
29
  end
24
30
 
31
+ ::Airbrake.notify_queue(
32
+ queue: action,
33
+ error_count: 1,
34
+ timing: 0.01,
35
+ )
36
+
25
37
  raise exception
38
+ else
39
+ ::Airbrake.notify_queue(
40
+ queue: job_class || job.payload_object.class.name,
41
+ error_count: 0,
42
+ timing: timing,
43
+ )
26
44
  end
27
45
  end
28
46
  end
29
47
  end
30
- # rubocop:enable Lint/RescueException
48
+ # rubocop:enable Metrics/BlockLength
31
49
  end
32
50
  end
33
51
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
  require 'delegate'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rack/user'
2
4
  require 'airbrake/rack/user_filter'
3
5
  require 'airbrake/rack/context_filter'
@@ -16,6 +18,8 @@ module Airbrake
16
18
  # @since v9.2.0
17
19
  # @api public
18
20
  def self.capture_timing(label)
21
+ return yield unless Airbrake::Config.instance.performance_stats
22
+
19
23
  routes = Airbrake::Rack::RequestStore[:routes]
20
24
  if !routes || routes.none?
21
25
  result = yield
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Adds context (URL, User-Agent, framework version, controller and more).
@@ -18,7 +20,12 @@ module Airbrake
18
20
  context = notice[:context]
19
21
 
20
22
  context[:url] = request.url
21
- context[:userAddr] = request.ip
23
+ context[:userAddr] =
24
+ if request.respond_to?(:remote_ip)
25
+ request.remote_ip
26
+ else
27
+ request.ip
28
+ end
22
29
  context[:userAgent] = request.user_agent
23
30
 
24
31
  add_framework_version(context)
@@ -49,7 +56,7 @@ module Airbrake
49
56
  else
50
57
  {
51
58
  'rack_version' => ::Rack.version,
52
- 'rack_release' => ::Rack.release
59
+ 'rack_release' => ::Rack.release,
53
60
  }
54
61
  end
55
62
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Adds HTTP request parameters.
@@ -6,10 +8,10 @@ module Airbrake
6
8
  class HttpHeadersFilter
7
9
  # @return [Array<String>] the prefixes of the majority of HTTP headers in
8
10
  # Rack (some prefixes match the header names for simplicity)
9
- HTTP_HEADER_PREFIXES = [
10
- 'HTTP_'.freeze,
11
- 'CONTENT_TYPE'.freeze,
12
- 'CONTENT_LENGTH'.freeze
11
+ HTTP_HEADER_PREFIXES = %w[
12
+ HTTP_
13
+ CONTENT_TYPE
14
+ CONTENT_LENGTH
13
15
  ].freeze
14
16
 
15
17
  # @return [Integer]
@@ -34,7 +36,7 @@ module Airbrake
34
36
  notice[:context].merge!(
35
37
  httpMethod: request.request_method,
36
38
  referer: request.referer,
37
- headers: http_headers
39
+ headers: http_headers,
38
40
  )
39
41
  end
40
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Adds HTTP request parameters.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Instrumentable holds methods that simplify instrumenting Rack apps.
@@ -15,14 +17,120 @@ module Airbrake
15
17
  # @since v9.2.0
16
18
  module Instrumentable
17
19
  def airbrake_capture_timing(method_name, label: method_name.to_s)
18
- alias_method "#{method_name}_without_airbrake", method_name
20
+ instrumentable = ::Airbrake::Rack::Instrumentable
21
+ if instrumentable.should_prepend?(self, method_name)
22
+ instrumentable.prepend_capture_timing(self, method_name, label)
23
+ else
24
+ instrumentable.chain_capture_timing(self, method_name, label)
25
+ end
26
+ method_name
27
+ end
28
+
29
+ # @api private
30
+ def __airbrake_capture_timing_module__
31
+ # Module used to store prepended wrapper methods, saved as an instance
32
+ # variable so each target class/module gets its own module. This just
33
+ # a convenience to avoid prepending a lot of anonymous modules.
34
+ @__airbrake_capture_timing_module__ ||= ::Module.new
35
+ end
36
+ private :__airbrake_capture_timing_module__
37
+
38
+ # Using api private self methods so they don't get defined in the target
39
+ # class or module, but can still be called by the above method.
40
+
41
+ # @api private
42
+ def self.should_prepend?(klass, method_name)
43
+ # Don't chain already-prepended or operator methods.
44
+ klass.module_exec do
45
+ self_class_idx = ancestors.index(self)
46
+ method_owner_idx = ancestors.index(instance_method(method_name).owner)
47
+ method_owner_idx < self_class_idx || !(/\A\W/ =~ method_name).nil?
48
+ end
49
+ end
50
+
51
+ # @api private
52
+ def self.prepend_capture_timing(klass, method_name, label)
53
+ args = method_signature
54
+ visibility = method_visibility(klass, method_name)
55
+
56
+ # Generate the wrapper method.
57
+ klass.module_exec do
58
+ mod = __airbrake_capture_timing_module__
59
+ mod.module_exec do
60
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
61
+ def #{method_name}(#{args})
62
+ Airbrake::Rack.capture_timing(#{label.to_s.inspect}) do
63
+ super
64
+ end
65
+ end
66
+ #{visibility} :#{method_name}
67
+ RUBY
68
+ end
69
+ prepend mod
70
+ end
71
+ end
72
+
73
+ # @api private
74
+ def self.chain_capture_timing(klass, method_name, label)
75
+ args = method_signature
76
+ visibility = method_visibility(klass, method_name)
19
77
 
20
- define_method(method_name) do |*args|
21
- Airbrake::Rack.capture_timing(label) do
22
- __send__("#{method_name}_without_airbrake", *args)
78
+ # Generate the wrapper method.
79
+ aliased = method_name.to_s.sub(/([?!=])$/, '')
80
+ punctuation = Regexp.last_match(1)
81
+ wrapped_method_name = "#{aliased}_without_airbrake#{punctuation}"
82
+ needs_removal = method_needs_removal(klass, method_name)
83
+ klass.module_exec do
84
+ alias_method wrapped_method_name, method_name
85
+ remove_method method_name if needs_removal
86
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
87
+ def #{method_name}(#{args})
88
+ Airbrake::Rack.capture_timing(#{label.to_s.inspect}) do
89
+ __send__("#{aliased}_without_airbrake#{punctuation}", #{args})
90
+ end
91
+ end
92
+ #{visibility} :#{method_name}
93
+ RUBY
94
+ end
95
+ end
96
+
97
+ # @api private
98
+ def self.method_visibility(klass, method_name)
99
+ klass.module_exec do
100
+ if protected_method_defined?(method_name)
101
+ "protected"
102
+ elsif private_method_defined?(method_name)
103
+ "private"
104
+ else
105
+ "public"
23
106
  end
24
107
  end
25
108
  end
109
+
110
+ # @api private
111
+ # A method instead of a constant so it isn't accessible in the target.
112
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7")
113
+ def self.method_signature
114
+ "*args, **kw_args, &block"
115
+ end
116
+ else
117
+ def self.method_signature
118
+ "*args, &block"
119
+ end
120
+ end
121
+
122
+ # @api private
123
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6")
124
+ def self.method_needs_removal(klass, method_name)
125
+ klass.method_defined?(method_name, false) ||
126
+ klass.private_method_defined?(method_name, false)
127
+ end
128
+ else
129
+ def self.method_needs_removal(klass, method_name)
130
+ klass.instance_methods(false).include?(method_name) ||
131
+ klass.private_instance_methods(false).include?(method_name)
132
+ end
133
+ end
26
134
  end
27
135
  end
28
136
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Airbrake Rack middleware for Rails and Sinatra applications (or any other
@@ -94,7 +96,7 @@ end
94
96
  Airbrake::Rack::SessionFilter,
95
97
  Airbrake::Rack::HttpParamsFilter,
96
98
  Airbrake::Rack::HttpHeadersFilter,
97
- Airbrake::Rack::RouteFilter
99
+ Airbrake::Rack::RouteFilter,
98
100
  ].each do |filter|
99
101
  Airbrake.add_filter(filter.new)
100
102
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # A filter that appends Rack request body to the notice.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # RequestStore is a thin (and limited) wrapper around *Thread.current* that
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'airbrake/rails/app'
4
+
1
5
  module Airbrake
2
6
  module Rack
3
7
  # Adds route slugs to context/route.
@@ -23,20 +27,14 @@ module Airbrake
23
27
  private
24
28
 
25
29
  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
30
+ return unless (route = Airbrake::Rails::App.recognize_route(request))
31
+
32
+ route.path
36
33
  end
37
34
 
38
35
  def sinatra_route(request)
39
36
  return unless (route = request.env['sinatra.route'])
37
+
40
38
  route.split(' ').drop(1).join(' ')
41
39
  end
42
40
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Adds HTTP session.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Represents an authenticated user, which can be converted to Airbrake's
@@ -26,6 +28,7 @@ module Airbrake
26
28
  controller = rack_env['action_controller.instance']
27
29
  return unless controller.respond_to?(:current_user, true)
28
30
  return unless [-1, 0].include?(controller.method(:current_user).arity)
31
+
29
32
  begin
30
33
  controller.__send__(:current_user)
31
34
  rescue Exception => _e # rubocop:disable Lint/RescueException
@@ -57,6 +60,7 @@ module Airbrake
57
60
  # try methods with no arguments or with variable number of arguments,
58
61
  # where none of them are required
59
62
  return unless @user.method(key).arity.between?(-1, 0)
63
+
60
64
  String(@user.__send__(key))
61
65
  end
62
66
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rack
3
5
  # Adds current user information.
@@ -1,19 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/railtie'
2
4
 
3
5
  module Airbrake
4
6
  # Rails namespace holds all Rails-related functionality.
5
7
  module Rails
6
8
  def self.logger
9
+ # Rails.logger is not set in some Rake tasks such as
10
+ # 'airbrake:deploy'. In this case we use a sensible fallback.
11
+ level = (::Rails.logger ? ::Rails.logger.level : Logger::ERROR)
12
+
7
13
  if ENV['RAILS_LOG_TO_STDOUT'].present?
8
- Logger.new(STDOUT, level: ::Rails.logger.level)
14
+ Logger.new(STDOUT, level: level)
9
15
  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
- )
16
+ Logger.new(::Rails.root.join('log', 'airbrake.log'), level: level)
17
17
  end
18
18
  end
19
19
  end