airbrake 9.5.3 → 10.0.2

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 +2 -0
  6. data/lib/airbrake/delayed_job.rb +24 -6
  7. data/lib/airbrake/logger.rb +2 -0
  8. data/lib/airbrake/rack.rb +2 -0
  9. data/lib/airbrake/rack/context_filter.rb +9 -2
  10. data/lib/airbrake/rack/http_headers_filter.rb +4 -2
  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 +3 -1
  17. data/lib/airbrake/rack/session_filter.rb +2 -0
  18. data/lib/airbrake/rack/user.rb +2 -0
  19. data/lib/airbrake/rack/user_filter.rb +2 -0
  20. data/lib/airbrake/rails.rb +3 -1
  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 +2 -0
  24. data/lib/airbrake/rails/action_controller_notify_subscriber.rb +4 -2
  25. data/lib/airbrake/rails/action_controller_performance_breakdown_subscriber.rb +4 -1
  26. data/lib/airbrake/rails/action_controller_route_subscriber.rb +5 -11
  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 +5 -3
  30. data/lib/airbrake/rails/app.rb +43 -9
  31. data/lib/airbrake/rails/backtrace_cleaner.rb +2 -0
  32. data/lib/airbrake/rails/curb.rb +2 -0
  33. data/lib/airbrake/rails/event.rb +3 -5
  34. data/lib/airbrake/rails/excon_subscriber.rb +2 -0
  35. data/lib/airbrake/rails/http.rb +2 -0
  36. data/lib/airbrake/rails/http_client.rb +2 -0
  37. data/lib/airbrake/rails/net_http.rb +14 -6
  38. data/lib/airbrake/rails/railtie.rb +11 -9
  39. data/lib/airbrake/rails/typhoeus.rb +2 -0
  40. data/lib/airbrake/rake.rb +3 -1
  41. data/lib/airbrake/rake/tasks.rb +6 -4
  42. data/lib/airbrake/resque.rb +32 -0
  43. data/lib/airbrake/shoryuken.rb +17 -3
  44. data/lib/airbrake/sidekiq.rb +20 -13
  45. data/lib/airbrake/sidekiq/retryable_jobs_filter.rb +2 -0
  46. data/lib/airbrake/sneakers.rb +38 -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 +2 -0
  50. metadata +19 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc5653068c80669e6ed4a43ffc993715337e59ae4b13169fbc9cf5b82e438804
4
- data.tar.gz: 272a62d259ac08b1cbd66db11b84fbbae272fb12aea9a6edefd6e3d9d26d18db
3
+ metadata.gz: 5138c1675426cc908ae8b1857898ddc04c135fda23dfe21db19e6b1bf0b36faf
4
+ data.tar.gz: '085abd9125a9a615935ac2b1171cdfd74205bde892708e073bfbda44103c72ca'
5
5
  SHA512:
6
- metadata.gz: ae7993740f12db8a622f4264157bf71cc9e84e7f2674a1d73e61202217823ea478f3f512cdc9eaeafb828517d5101417be65869c51d1a27624bc22ae9724cbc6
7
- data.tar.gz: 2bc63b888f983a7622a1e63caa4d6cc76597aacbfcb28dcc79484da038ee2cafc91773edb40a3061b771f091579a5b3f701b5c456f2d7aba35ac0b3af76d03e7
6
+ metadata.gz: 39bc3cfe3510b0db2adece334666b4e753298fd4d09f4d7d445086c0512aa0436ec8c5b38a85933fae822efeea155d7d2bea7e25866e169878b6f1b330fb4d4d
7
+ data.tar.gz: 9c189959ccdfa6c30752462dffb49e8925df101f18597180ab6a31e2320a620431bcbcf6d66c83838d1acfd9be1b6c98623253e60cea77e8881fc84e5996aa14
@@ -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
@@ -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_sync(
32
+ queue: action,
33
+ error_count: 1,
34
+ timing: 0.01,
35
+ )
36
+
25
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
+ )
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'
@@ -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.
@@ -9,7 +11,7 @@ module Airbrake
9
11
  HTTP_HEADER_PREFIXES = [
10
12
  'HTTP_'.freeze,
11
13
  'CONTENT_TYPE'.freeze,
12
- 'CONTENT_LENGTH'.freeze
14
+ 'CONTENT_LENGTH'.freeze,
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".freeze
102
+ elsif private_method_defined?(method_name)
103
+ "private".freeze
104
+ else
105
+ "public".freeze
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".freeze
115
+ end
116
+ else
117
+ def self.method_signature
118
+ "*args, &block".freeze
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,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/app'
2
4
 
3
5
  module Airbrake
@@ -26,7 +28,7 @@ module Airbrake
26
28
 
27
29
  def rails_route(request)
28
30
  return unless (route = Airbrake::Rails::App.recognize_route(request))
29
- route.path.spec.to_s
31
+ route.path
30
32
  end
31
33
 
32
34
  def sinatra_route(request)
@@ -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
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/railtie'
2
4
 
3
5
  module Airbrake
@@ -12,7 +14,7 @@ module Airbrake
12
14
 
13
15
  # Rails.logger is not set in some Rake tasks such as
14
16
  # 'airbrake:deploy'. In this case we use a sensible fallback.
15
- level: (::Rails.logger ? ::Rails.logger.level : Logger::ERROR)
17
+ level: (::Rails.logger ? ::Rails.logger.level : Logger::ERROR),
16
18
  )
17
19
  end
18
20
  end
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/event'
2
4
 
3
5
  module Airbrake
@@ -18,8 +20,8 @@ module Airbrake
18
20
  method: event.method,
19
21
  route: route,
20
22
  status_code: event.status_code,
21
- start_time: event.time,
22
- end_time: Time.new
23
+ timing: event.duration,
24
+ time: event.time,
23
25
  )
24
26
  end
25
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/event'
2
4
 
3
5
  module Airbrake
@@ -20,7 +22,8 @@ module Airbrake
20
22
  route: route,
21
23
  response_type: event.response_type,
22
24
  groups: groups,
23
- start_time: event.time
25
+ timing: event.duration,
26
+ time: event.time,
24
27
  }
25
28
 
26
29
  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
 
@@ -14,24 +16,16 @@ module Airbrake
14
16
 
15
17
  event = Airbrake::Rails::Event.new(*args)
16
18
  route = Airbrake::Rails::App.recognize_route(
17
- Airbrake::Rack::RequestStore[:request]
19
+ Airbrake::Rack::RequestStore[:request],
18
20
  )
19
21
  return unless route
20
22
 
21
- routes[find_route_name(route)] = {
23
+ routes[route.path] = {
22
24
  method: event.method,
23
25
  response_type: event.response_type,
24
- groups: {}
26
+ groups: {},
25
27
  }
26
28
  end
27
-
28
- def find_route_name(route)
29
- if route.app.respond_to?(:app) && route.app.app.respond_to?(:engine_name)
30
- "#{route.app.app.engine_name}##{route.path.spec}"
31
- else
32
- route.path.spec.to_s
33
- end
34
- end
35
29
  end
36
30
  end
37
31
  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_sync(
26
+ queue: job.class.name,
27
+ error_count: 1,
28
+ timing: 0.01,
29
+ )
30
+ raise exception
31
+ else
32
+ Airbrake.notify_queue_sync(
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
 
@@ -22,8 +24,8 @@ module Airbrake
22
24
  func: frame[:function],
23
25
  file: frame[:file],
24
26
  line: frame[:line],
25
- start_time: event.time,
26
- end_time: event.end
27
+ timing: event.duration,
28
+ time: event.time,
27
29
  )
28
30
  end
29
31
  end
@@ -33,7 +35,7 @@ module Airbrake
33
35
  def last_caller
34
36
  exception = StandardError.new
35
37
  exception.set_backtrace(
36
- Airbrake::Rails::BacktraceCleaner.clean(Kernel.caller)
38
+ Airbrake::Rails::BacktraceCleaner.clean(Kernel.caller),
37
39
  )
38
40
  Airbrake::Backtrace.parse(exception).first || {}
39
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rails
3
5
  # App is a wrapper around Rails.application.
@@ -5,18 +7,50 @@ module Airbrake
5
7
  # @since v9.0.3
6
8
  # @api private
7
9
  class App
10
+ Route = Struct.new(:path)
11
+
12
+ # @param [] request
13
+ # @return [Airbrake::Rails::App::Route, nil]
8
14
  def self.recognize_route(request)
9
- ::Rails.application.routes.router.recognize(request) do |route, _params|
10
- # Rails can recognize multiple routes for the given request. For
11
- # example, if we visit /users/2/edit, then Rails sees these routes:
12
- # * "/users/:id/edit(.:format)"
13
- # * "/"
14
- #
15
- # We return the first route as, what it seems, the most optimal
16
- # approach.
17
- return route
15
+ # Duplicate `request` because `recognize` *can* strip the request's
16
+ # `path_info`, which results in broken engine links (when the engine has
17
+ # an isolated namespace).
18
+ request_copy = request.dup
19
+
20
+ # We must search every engine individually to find a concrete route. If
21
+ # we rely only on the `Rails.application.routes.router`, then the
22
+ # recognize call would return the root route, neglecting PATH_INFO
23
+ # completely. For example:
24
+ # * a request is made to `marketing#pricing`
25
+ # * `Rails.application` recognizes it as `marketing#/` (incorrect)
26
+ # * `Marketing::Engine` recognizes it as `marketing#/pricing` (correct)
27
+ engines.each do |engine|
28
+ engine.routes.router.recognize(request_copy) do |route, _params|
29
+ path =
30
+ if engine == ::Rails.application
31
+ route.path.spec.to_s
32
+ else
33
+ "#{engine.engine_name}##{route.path.spec}"
34
+ end
35
+
36
+ # Rails can recognize multiple routes for the given request. For
37
+ # example, if we visit /users/2/edit, then Rails sees these routes:
38
+ # * "/users/:id/edit(.:format)"
39
+ # * "/"
40
+ #
41
+ # We return the first route as, what it seems, the most optimal
42
+ # approach.
43
+ return Route.new(path)
44
+ end
18
45
  end
46
+
47
+ nil
19
48
  end
49
+
50
+ def self.engines
51
+ @engines ||= [*::Rails::Engine.subclasses, ::Rails.application]
52
+ end
53
+ private_class_method :engines
20
54
  end
21
55
  end
22
56
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rails
3
5
  # BacktraceCleaner is a wrapper around Rails.backtrace_cleaner.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Curl
2
4
  # Monkey-patch to measure request timing.
3
5
  class Easy
@@ -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.
@@ -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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/rails/event'
2
4
 
3
5
  module Airbrake
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP
2
4
  # Monkey-patch to measure request timing.
3
5
  class Client
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Monkey-patch to measure request timing.
2
4
  class HTTPClient
3
5
  alias do_get_without_airbrake do_get_block
@@ -1,10 +1,18 @@
1
- # Monkey-patch Net::HTTP to benchmark it.
2
- Net::HTTP.class_eval do
3
- alias_method :request_without_airbrake, :request
1
+ # frozen_string_literal: true
4
2
 
5
- def request(request, *args, &block)
6
- Airbrake::Rack.capture_timing(:http) do
7
- request_without_airbrake(request, *args, &block)
3
+ module Airbrake
4
+ module Rails
5
+ # Monkey-patch Net::HTTP to benchmark it.
6
+ # @api private
7
+ # @since v10.0.2
8
+ module NetHttp
9
+ def request(request, *args, &block)
10
+ Airbrake::Rack.capture_timing(:http) do
11
+ super(request, *args, &block)
12
+ end
13
+ end
8
14
  end
9
15
  end
10
16
  end
17
+
18
+ Net::HTTP.prepend(Airbrake::Rails::NetHttp)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Rails
3
5
  # This railtie works for any Rails application that supports railties (Rails
4
6
  # 3.2+ apps). It makes Airbrake Ruby work with Rails and report errors
5
7
  # occurring in the application automatically.
6
8
  class Railtie < ::Rails::Railtie
7
- initializer('airbrake.middleware') do |app|
9
+ initializer('airbrake.middleware', after: :load_config_initializers) do |app|
8
10
  # Since Rails 3.2 the ActionDispatch::DebugExceptions middleware is
9
11
  # responsible for logging exceptions and showing a debugging page in
10
12
  # case the request is local. We want to insert our middleware after
@@ -16,20 +18,20 @@ module Airbrake
16
18
  # exist in Rails 5 anymore.
17
19
  app.config.middleware.insert_after(
18
20
  ActionDispatch::DebugExceptions,
19
- Airbrake::Rack::Middleware
21
+ Airbrake::Rack::Middleware,
20
22
  )
21
23
  elsif defined?(::ActiveRecord::ConnectionAdapters::ConnectionManagement)
22
24
  # Insert after ConnectionManagement to avoid DB connection leakage:
23
25
  # https://github.com/airbrake/airbrake/pull/568
24
26
  app.config.middleware.insert_after(
25
27
  ::ActiveRecord::ConnectionAdapters::ConnectionManagement,
26
- 'Airbrake::Rack::Middleware'
28
+ 'Airbrake::Rack::Middleware',
27
29
  )
28
30
  else
29
31
  # Insert after DebugExceptions for apps without ActiveRecord.
30
32
  app.config.middleware.insert_after(
31
33
  ActionDispatch::DebugExceptions,
32
- 'Airbrake::Rack::Middleware'
34
+ 'Airbrake::Rack::Middleware',
33
35
  )
34
36
  end
35
37
  end
@@ -55,21 +57,21 @@ module Airbrake
55
57
  require 'airbrake/rails/action_controller_route_subscriber'
56
58
  ActiveSupport::Notifications.subscribe(
57
59
  'start_processing.action_controller',
58
- Airbrake::Rails::ActionControllerRouteSubscriber.new
60
+ Airbrake::Rails::ActionControllerRouteSubscriber.new,
59
61
  )
60
62
 
61
63
  # Send route stats.
62
64
  require 'airbrake/rails/action_controller_notify_subscriber'
63
65
  ActiveSupport::Notifications.subscribe(
64
66
  'process_action.action_controller',
65
- Airbrake::Rails::ActionControllerNotifySubscriber.new
67
+ Airbrake::Rails::ActionControllerNotifySubscriber.new,
66
68
  )
67
69
 
68
70
  # Send performance breakdown: where a request spends its time.
69
71
  require 'airbrake/rails/action_controller_performance_breakdown_subscriber'
70
72
  ActiveSupport::Notifications.subscribe(
71
73
  'process_action.action_controller',
72
- Airbrake::Rails::ActionControllerPerformanceBreakdownSubscriber.new
74
+ Airbrake::Rails::ActionControllerPerformanceBreakdownSubscriber.new,
73
75
  )
74
76
 
75
77
  require 'airbrake/rails/net_http' if defined?(Net) && defined?(Net::HTTP)
@@ -112,8 +114,8 @@ module Airbrake
112
114
  # Filter out parameters from SQL body.
113
115
  Airbrake.add_performance_filter(
114
116
  Airbrake::Filters::SqlFilter.new(
115
- ::ActiveRecord::Base.connection_config[:adapter]
116
- )
117
+ ::ActiveRecord::Base.connection_config[:adapter],
118
+ ),
117
119
  )
118
120
  end
119
121
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Typhoeus
2
4
  # Monkey-patch to measure request timing.
3
5
  class Request
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This is not bulletproof, but if this file is executed before a task
2
4
  # definition, we can grab tasks descriptions and locations.
3
5
  # See: https://goo.gl/ksn6PE
@@ -30,7 +32,7 @@ module Rake
30
32
  notice[:params].merge!(
31
33
  rake_task: task_info,
32
34
  execute_args: args,
33
- argv: ARGV.join(' ')
35
+ argv: ARGV.join(' '),
34
36
  )
35
37
 
36
38
  Airbrake.notify_sync(notice)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake-ruby'
2
4
 
3
5
  namespace :airbrake do
@@ -55,7 +57,7 @@ namespace :airbrake do
55
57
  username: ENV['USERNAME'],
56
58
  revision: ENV['REVISION'],
57
59
  repository: ENV['REPOSITORY'],
58
- version: ENV['VERSION']
60
+ version: ENV['VERSION'],
59
61
  }
60
62
  promise = Airbrake.notify_deploy(deploy_params)
61
63
  promise.then do
@@ -97,14 +99,14 @@ namespace :airbrake do
97
99
  end
98
100
  end
99
101
 
100
- url = "https://airbrake.io/api/v3/projects/#{id}/heroku-deploys?key=#{key}"
102
+ url = ["https://airbrake.io/api/v3/projects/#{id}/heroku-deploys?key=#{key}"]
101
103
  url << "&environment=#{env}"
102
104
  url << "&repository=#{repo}" unless repo.empty?
103
105
 
104
- command = %(heroku addons:create deployhooks:http --url="#{url}")
106
+ command = [%(heroku addons:create deployhooks:http --url="#{url.join}")]
105
107
  command << " --app #{app}" if app
106
108
 
107
- puts "$ #{command}"
109
+ puts "$ #{command.join}"
108
110
  Bundler.with_clean_env { puts `#{command}` }
109
111
  end
110
112
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Resque
2
4
  module Failure
3
5
  # Provides Resque integration with Airbrake.
@@ -27,3 +29,33 @@ module Resque
27
29
  end
28
30
  end
29
31
  end
32
+
33
+ module Resque
34
+ # Measures elapsed time of a job and notifies Airbrake of the execution
35
+ # status.
36
+ #
37
+ # @since v9.6.0
38
+ class Job
39
+ # Store the original method to use it later.
40
+ alias perform_without_airbrake perform
41
+
42
+ def perform
43
+ timing = Airbrake::Benchmark.measure do
44
+ perform_without_airbrake
45
+ end
46
+ rescue StandardError => exception
47
+ Airbrake.notify_queue_sync(
48
+ queue: payload['class'],
49
+ error_count: 1,
50
+ timing: 0.01,
51
+ )
52
+ raise exception
53
+ else
54
+ Airbrake.notify_queue_sync(
55
+ queue: payload['class'],
56
+ error_count: 0,
57
+ timing: timing,
58
+ )
59
+ end
60
+ end
61
+ end
@@ -1,14 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Shoryuken
3
5
  # Provides integration with Shoryuken.
4
6
  class ErrorHandler
5
7
  # rubocop:disable Lint/RescueException
6
8
  def call(worker, queue, _sqs_msg, body)
7
- yield
9
+ timing = Airbrake::Benchmark.measure do
10
+ yield
11
+ end
8
12
  rescue Exception => exception
9
13
  notify_airbrake(exception, worker, queue, body)
10
-
14
+ Airbrake.notify_queue(
15
+ queue: worker.class.to_s,
16
+ error_count: 1,
17
+ timing: 0.01,
18
+ )
11
19
  raise exception
20
+ else
21
+ Airbrake.notify_queue(
22
+ queue: worker.class.to_s,
23
+ error_count: 0,
24
+ timing: timing,
25
+ )
12
26
  end
13
27
  # rubocop:enable Lint/RescueException
14
28
 
@@ -24,7 +38,7 @@ module Airbrake
24
38
  def notice_context(queue, body)
25
39
  {
26
40
  queue: queue,
27
- body: body.is_a?(Array) ? { batch: body } : { body: body }
41
+ body: body.is_a?(Array) ? { batch: body } : { body: body },
28
42
  }
29
43
  end
30
44
  end
@@ -1,17 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'airbrake/sidekiq/retryable_jobs_filter'
2
4
 
3
5
  module Airbrake
4
6
  module Sidekiq
5
7
  # Provides integration with Sidekiq v2+.
6
8
  class ErrorHandler
7
- # rubocop:disable Lint/RescueException
8
9
  def call(_worker, context, _queue)
9
- yield
10
- rescue Exception => exception
10
+ timing = Airbrake::Benchmark.measure do
11
+ yield
12
+ end
13
+ rescue Exception => exception # rubocop:disable Lint/RescueException
11
14
  notify_airbrake(exception, context)
15
+ Airbrake.notify_queue(
16
+ queue: context['class'],
17
+ error_count: 1,
18
+ timing: 0.01,
19
+ )
12
20
  raise exception
21
+ else
22
+ Airbrake.notify_queue(
23
+ queue: context['class'],
24
+ error_count: 0,
25
+ timing: timing,
26
+ )
13
27
  end
14
- # rubocop:enable Lint/RescueException
15
28
 
16
29
  private
17
30
 
@@ -34,14 +47,8 @@ module Airbrake
34
47
  end
35
48
  end
36
49
 
37
- if Sidekiq::VERSION < '3'
38
- Sidekiq.configure_server do |config|
39
- config.server_middleware do |chain|
40
- chain.add(Airbrake::Sidekiq::ErrorHandler)
41
- end
42
- end
43
- else
44
- Sidekiq.configure_server do |config|
45
- config.error_handlers << Airbrake::Sidekiq::ErrorHandler.new.method(:notify_airbrake)
50
+ Sidekiq.configure_server do |config|
51
+ config.server_middleware do |chain|
52
+ chain.add(Airbrake::Sidekiq::ErrorHandler)
46
53
  end
47
54
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Sidekiq
3
5
  # Filter that can ignore notices from jobs that failed but will be retried
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Airbrake
2
4
  module Sneakers
3
5
  # Provides integration with Sneakers.
@@ -10,12 +12,15 @@ module Airbrake
10
12
  # @see https://github.com/airbrake/airbrake/issues/850
11
13
  IGNORED_KEYS = %i[delivery_tag consumer channel].freeze
12
14
 
13
- def call(exception, worker = nil, **context)
15
+ # rubocop:disable Style/OptionalArguments
16
+ def call(exception, worker = nil, context)
17
+ # Later versions add a middle argument.
14
18
  Airbrake.notify(exception, filter_context(context)) do |notice|
15
19
  notice[:context][:component] = 'sneakers'
16
20
  notice[:context][:action] = worker.class.to_s
17
21
  end
18
22
  end
23
+ # rubocop:enable Style/OptionalArguments
19
24
 
20
25
  private
21
26
 
@@ -32,3 +37,35 @@ module Airbrake
32
37
  end
33
38
 
34
39
  Sneakers.error_reporters << Airbrake::Sneakers::ErrorReporter.new
40
+
41
+ module Sneakers
42
+ # @todo Migrate to Sneakers v2.12.0 middleware API when it's released
43
+ # @see https://github.com/jondot/sneakers/pull/364
44
+ module Worker
45
+ # Sneakers v2.7.0+ renamed `do_work` to `process_work`.
46
+ if method_defined?(:process_work)
47
+ alias process_work_without_airbrake process_work
48
+ else
49
+ alias process_work_without_airbrake do_work
50
+ end
51
+
52
+ def process_work(delivery_info, metadata, msg, handler)
53
+ timing = Airbrake::Benchmark.measure do
54
+ process_work_without_airbrake(delivery_info, metadata, msg, handler)
55
+ end
56
+ rescue Exception => exception # rubocop:disable Lint/RescueException
57
+ Airbrake.notify_queue(
58
+ queue: self.class.to_s,
59
+ error_count: 1,
60
+ timing: 0.01,
61
+ )
62
+ raise exception
63
+ else
64
+ Airbrake.notify_queue(
65
+ queue: self.class.to_s,
66
+ error_count: 0,
67
+ timing: timing,
68
+ )
69
+ end
70
+ end
71
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # We use Semantic Versioning v2.0.0
2
4
  # More information: http://semver.org/
3
5
  module Airbrake
4
- AIRBRAKE_VERSION = '9.5.3'.freeze
6
+ AIRBRAKE_VERSION = '10.0.2'.freeze
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Creates the Airbrake initializer file for Rails apps.
2
4
  #
3
5
  # @example Invokation from terminal
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Airbrake is an online tool that provides robust exception tracking in your Rails
2
4
  # applications. In doing so, it allows you to easily review errors, tie an error
3
5
  # to an individual piece of code, and trace the cause back to recent
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.5.3
4
+ version: 10.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-27 00:00:00.000000000 Z
11
+ date: 2020-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: airbrake-ruby
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.8'
19
+ version: '4.13'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.8'
26
+ version: '4.13'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -254,6 +254,20 @@ dependencies:
254
254
  - - '='
255
255
  - !ruby/object:Gem::Version
256
256
  version: 1.9.0
257
+ - !ruby/object:Gem::Dependency
258
+ name: redis-namespace
259
+ requirement: !ruby/object:Gem::Requirement
260
+ requirements:
261
+ - - '='
262
+ - !ruby/object:Gem::Version
263
+ version: 1.6.0
264
+ type: :development
265
+ prerelease: false
266
+ version_requirements: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - '='
269
+ - !ruby/object:Gem::Version
270
+ version: 1.6.0
257
271
  - !ruby/object:Gem::Dependency
258
272
  name: sidekiq
259
273
  requirement: !ruby/object:Gem::Requirement
@@ -421,8 +435,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
421
435
  - !ruby/object:Gem::Version
422
436
  version: '0'
423
437
  requirements: []
424
- rubyforge_project:
425
- rubygems_version: 2.7.6.2
438
+ rubygems_version: 3.1.2
426
439
  signing_key:
427
440
  specification_version: 4
428
441
  summary: Airbrake is an online tool that provides robust exception tracking in any