celerbrake 0.1.0

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/lib/celerbrake/capistrano/capistrano2.rb +40 -0
  3. data/lib/celerbrake/capistrano/capistrano3.rb +23 -0
  4. data/lib/celerbrake/capistrano.rb +8 -0
  5. data/lib/celerbrake/delayed_job.rb +64 -0
  6. data/lib/celerbrake/logger.rb +105 -0
  7. data/lib/celerbrake/rack/context_filter.rb +69 -0
  8. data/lib/celerbrake/rack/http_headers_filter.rb +44 -0
  9. data/lib/celerbrake/rack/http_params_filter.rb +27 -0
  10. data/lib/celerbrake/rack/instrumentable.rb +140 -0
  11. data/lib/celerbrake/rack/middleware.rb +102 -0
  12. data/lib/celerbrake/rack/request_body_filter.rb +33 -0
  13. data/lib/celerbrake/rack/request_store.rb +34 -0
  14. data/lib/celerbrake/rack/route_filter.rb +51 -0
  15. data/lib/celerbrake/rack/session_filter.rb +25 -0
  16. data/lib/celerbrake/rack/user.rb +74 -0
  17. data/lib/celerbrake/rack/user_filter.rb +25 -0
  18. data/lib/celerbrake/rack.rb +39 -0
  19. data/lib/celerbrake/rails/action_cable/notify_callback.rb +22 -0
  20. data/lib/celerbrake/rails/action_cable.rb +37 -0
  21. data/lib/celerbrake/rails/action_controller.rb +40 -0
  22. data/lib/celerbrake/rails/action_controller_notify_subscriber.rb +32 -0
  23. data/lib/celerbrake/rails/action_controller_performance_breakdown_subscriber.rb +51 -0
  24. data/lib/celerbrake/rails/action_controller_route_subscriber.rb +33 -0
  25. data/lib/celerbrake/rails/active_job.rb +50 -0
  26. data/lib/celerbrake/rails/active_record.rb +36 -0
  27. data/lib/celerbrake/rails/active_record_subscriber.rb +46 -0
  28. data/lib/celerbrake/rails/app.rb +78 -0
  29. data/lib/celerbrake/rails/backtrace_cleaner.rb +23 -0
  30. data/lib/celerbrake/rails/curb.rb +32 -0
  31. data/lib/celerbrake/rails/event.rb +93 -0
  32. data/lib/celerbrake/rails/excon_subscriber.rb +25 -0
  33. data/lib/celerbrake/rails/http.rb +18 -0
  34. data/lib/celerbrake/rails/http_client.rb +16 -0
  35. data/lib/celerbrake/rails/net_http.rb +18 -0
  36. data/lib/celerbrake/rails/railtie.rb +54 -0
  37. data/lib/celerbrake/rails/railties/action_controller_tie.rb +90 -0
  38. data/lib/celerbrake/rails/railties/active_record_tie.rb +74 -0
  39. data/lib/celerbrake/rails/railties/middleware_tie.rb +62 -0
  40. data/lib/celerbrake/rails/typhoeus.rb +16 -0
  41. data/lib/celerbrake/rails.rb +32 -0
  42. data/lib/celerbrake/rake/tasks.rb +112 -0
  43. data/lib/celerbrake/rake.rb +66 -0
  44. data/lib/celerbrake/resque.rb +62 -0
  45. data/lib/celerbrake/shoryuken.rb +52 -0
  46. data/lib/celerbrake/sidekiq/retryable_jobs_filter.rb +53 -0
  47. data/lib/celerbrake/sidekiq.rb +53 -0
  48. data/lib/celerbrake/sneakers.rb +72 -0
  49. data/lib/celerbrake/version.rb +7 -0
  50. data/lib/celerbrake.rb +32 -0
  51. data/lib/generators/celerbrake_generator.rb +22 -0
  52. data/lib/generators/celerbrake_initializer.rb.erb +80 -0
  53. metadata +380 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rack
5
+ # RequestStore is a thin (and limited) wrapper around *Thread.current* that
6
+ # allows writing and reading thread-local variables under the +:celerbrake+
7
+ # key.
8
+ # @api private
9
+ # @since v8.1.3
10
+ module RequestStore
11
+ class << self
12
+ # @return [Hash] a hash for all request-related data
13
+ def store
14
+ Thread.current[:celerbrake] ||= {}
15
+ end
16
+
17
+ # @return [void]
18
+ def []=(key, value)
19
+ store[key] = value
20
+ end
21
+
22
+ # @return [Object]
23
+ def [](key)
24
+ store[key]
25
+ end
26
+
27
+ # @return [void]
28
+ def clear
29
+ Thread.current[:celerbrake] = {}
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rails/app'
4
+
5
+ module Celerbrake
6
+ module Rack
7
+ # Adds route slugs to context/route.
8
+ # @since v7.5.0
9
+ class RouteFilter
10
+ attr_reader :weight
11
+
12
+ def initialize
13
+ @weight = 100
14
+ end
15
+
16
+ def call(notice)
17
+ return unless (request = notice.stash[:rack_request])
18
+
19
+ notice[:context][:route] =
20
+ if action_dispatch_request?(request)
21
+ rails_route(request)
22
+ elsif sinatra_request?(request)
23
+ sinatra_route(request)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def rails_route(request)
30
+ return unless (route = Celerbrake::Rails::App.recognize_route(request))
31
+
32
+ route.path
33
+ end
34
+
35
+ def sinatra_route(request)
36
+ return unless (route = request.env['sinatra.route'])
37
+
38
+ route.split.drop(1).join(' ')
39
+ end
40
+
41
+ def action_dispatch_request?(request)
42
+ defined?(ActionDispatch::Request) &&
43
+ request.instance_of?(ActionDispatch::Request)
44
+ end
45
+
46
+ def sinatra_request?(request)
47
+ defined?(Sinatra::Request) && request.instance_of?(Sinatra::Request)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rack
5
+ # Adds HTTP session.
6
+ #
7
+ # @since v5.7.0
8
+ class SessionFilter
9
+ # @return [Integer]
10
+ attr_reader :weight
11
+
12
+ def initialize
13
+ @weight = 96
14
+ end
15
+
16
+ # @see Celerbrake::FilterChain#refine
17
+ def call(notice)
18
+ return unless (request = notice.stash[:rack_request])
19
+
20
+ session = request.session
21
+ notice[:session] = session if session
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rack
5
+ # Represents an authenticated user, which can be converted to Celerbrake's
6
+ # payload format. Supports Warden and Omniauth authentication frameworks.
7
+ class User
8
+ # Finds the user in the Rack environment and creates a new user wrapper.
9
+ #
10
+ # @param [Hash{String=>Object}] rack_env The Rack environment
11
+ # @return [Celerbrake::Rack::User, nil]
12
+ def self.extract(rack_env)
13
+ # Warden support (including Devise).
14
+ if (warden = rack_env['warden'])
15
+ user = warden.user(run_callbacks: false)
16
+ # Early return to prevent unwanted possible authentication via
17
+ # calling the `current_user` method later.
18
+ # See: https://github.com/celerbrake/celerbrake/issues/641
19
+ return user ? new(user) : nil
20
+ end
21
+
22
+ # Fallback mode (OmniAuth support included). Works only for Rails.
23
+ user = try_current_user(rack_env)
24
+ new(user) if user
25
+ end
26
+
27
+ def self.try_current_user(rack_env)
28
+ controller = rack_env['action_controller.instance']
29
+ return unless controller.respond_to?(:current_user, true)
30
+ return unless [-1, 0].include?(controller.method(:current_user).arity)
31
+
32
+ begin
33
+ controller.__send__(:current_user)
34
+ rescue Exception => _e # rubocop:disable Lint/RescueException
35
+ nil
36
+ end
37
+ end
38
+ private_class_method :try_current_user
39
+
40
+ def initialize(user)
41
+ @user = user
42
+ end
43
+
44
+ def as_json
45
+ user = {}
46
+
47
+ user[:id] = try_to_get(:id)
48
+ user[:name] = full_name
49
+ user[:username] = try_to_get(:username)
50
+ user[:email] = try_to_get(:email)
51
+
52
+ user = user.delete_if { |_key, val| val.nil? }
53
+ user.empty? ? user : { user: user }
54
+ end
55
+
56
+ private
57
+
58
+ def try_to_get(key)
59
+ return unless @user.respond_to?(key)
60
+ # try methods with no arguments or with variable number of arguments,
61
+ # where none of them are required
62
+ return unless @user.method(key).arity.between?(-1, 0)
63
+
64
+ String(@user.__send__(key))
65
+ end
66
+
67
+ def full_name
68
+ # Try to get first and last names. If that fails, try to get just 'name'.
69
+ name = [try_to_get(:first_name), try_to_get(:last_name)].compact.join(' ')
70
+ name.empty? ? try_to_get(:name) : name
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rack
5
+ # Adds current user information.
6
+ #
7
+ # @since v8.0.1
8
+ class UserFilter
9
+ # @return [Integer]
10
+ attr_reader :weight
11
+
12
+ def initialize
13
+ @weight = 99
14
+ end
15
+
16
+ # @see Celerbrake::FilterChain#refine
17
+ def call(notice)
18
+ return unless (request = notice.stash[:rack_request])
19
+
20
+ user = Celerbrake::Rack::User.extract(request.env)
21
+ notice[:context].merge!(user.as_json) if user
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rack/user'
4
+ require 'celerbrake/rack/user_filter'
5
+ require 'celerbrake/rack/context_filter'
6
+ require 'celerbrake/rack/session_filter'
7
+ require 'celerbrake/rack/http_params_filter'
8
+ require 'celerbrake/rack/http_headers_filter'
9
+ require 'celerbrake/rack/request_body_filter'
10
+ require 'celerbrake/rack/route_filter'
11
+ require 'celerbrake/rack/middleware'
12
+ require 'celerbrake/rack/request_store'
13
+ require 'celerbrake/rack/instrumentable'
14
+
15
+ module Celerbrake
16
+ # Rack is a namespace for all Rack-related code.
17
+ module Rack
18
+ # @since v9.2.0
19
+ # @api public
20
+ def self.capture_timing(label)
21
+ return yield unless Celerbrake::Config.instance.performance_stats
22
+
23
+ routes = Celerbrake::Rack::RequestStore[:routes]
24
+ if !routes || routes.none?
25
+ result = yield
26
+ else
27
+ timed_trace = Celerbrake::TimedTrace.span(label) do
28
+ result = yield
29
+ end
30
+
31
+ routes.each do |_route_path, params|
32
+ params[:groups].merge!(timed_trace.spans)
33
+ end
34
+ end
35
+
36
+ result
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rails
5
+ module ActionCable
6
+ # @since v8.3.0
7
+ # @api private
8
+ class NotifyCallback
9
+ def self.call(channel, block)
10
+ block.call
11
+ rescue Exception => ex # rubocop:disable Lint/RescueException
12
+ notice = Celerbrake.build_notice(ex)
13
+ notice[:context][:component] = 'action_cable'
14
+ notice[:context][:action] = channel.channel_name
15
+ Celerbrake.notify(notice)
16
+
17
+ raise ex
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rails/action_cable/notify_callback'
4
+
5
+ %i[subscribe unsubscribe].each do |callback_name|
6
+ ActionCable::Channel::Base.set_callback(
7
+ callback_name, :around, prepend: true
8
+ ) do |channel, inner|
9
+ Celerbrake::Rails::ActionCable::NotifyCallback.call(channel, inner)
10
+ end
11
+ end
12
+
13
+ module Celerbrake
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
+ Celerbrake.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
29
+
30
+ raise ex
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ ActionCable::Channel::Base.prepend(Celerbrake::ActionCable::Channel::Base)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rails
5
+ # Contains helper methods that can be used inside Rails controllers to send
6
+ # notices to Celerbrake. The main benefit of using them instead of the direct
7
+ # API is that they automatically add information from the Rack environment
8
+ # to notices.
9
+ module ActionController
10
+ private
11
+
12
+ # A helper method for sending notices to Celerbrake *asynchronously*.
13
+ # Attaches information from the Rack env.
14
+ # @see Celerbrake#notify, #notify_celerbrake_sync
15
+ def notify_celerbrake(exception, params = {}, &block)
16
+ return unless (notice = build_notice(exception, params))
17
+
18
+ Celerbrake.notify(notice, params, &block)
19
+ end
20
+
21
+ # A helper method for sending notices to Celerbrake *synchronously*.
22
+ # Attaches information from the Rack env.
23
+ # @see Celerbrake#notify_sync, #notify_celerbrake
24
+ def notify_celerbrake_sync(exception, params = {}, &block)
25
+ return unless (notice = build_notice(exception, params))
26
+
27
+ Celerbrake.notify_sync(notice, params, &block)
28
+ end
29
+
30
+ # @param [Exception] exception
31
+ # @return [Celerbrake::Notice] the notice with information from the Rack env
32
+ def build_notice(exception, params = {})
33
+ return unless (notice = Celerbrake.build_notice(exception, params))
34
+
35
+ notice.stash[:rack_request] = request
36
+ notice
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rails/event'
4
+
5
+ module Celerbrake
6
+ module Rails
7
+ # ActionControllerNotifySubscriber sends route stat information, including
8
+ # performance data.
9
+ #
10
+ # @since v8.0.0
11
+ class ActionControllerNotifySubscriber
12
+ def call(*args)
13
+ return unless Celerbrake::Config.instance.performance_stats
14
+
15
+ routes = Celerbrake::Rack::RequestStore[:routes]
16
+ return if !routes || routes.none?
17
+
18
+ event = Celerbrake::Rails::Event.new(*args)
19
+
20
+ routes.each do |route, _params|
21
+ Celerbrake.notify_request(
22
+ method: event.method,
23
+ route: route,
24
+ status_code: event.status_code,
25
+ timing: event.duration,
26
+ time: event.time,
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rails/event'
4
+
5
+ module Celerbrake
6
+ module Rails
7
+ # @since v8.3.0
8
+ class ActionControllerPerformanceBreakdownSubscriber
9
+ def call(*args)
10
+ return unless Celerbrake::Config.instance.performance_stats
11
+
12
+ routes = Celerbrake::Rack::RequestStore[:routes]
13
+ return if !routes || routes.none?
14
+
15
+ event = Celerbrake::Rails::Event.new(*args)
16
+ stash = build_stash
17
+
18
+ routes.each do |route, params|
19
+ groups = event.groups.merge(params[:groups])
20
+ next if groups.none?
21
+
22
+ breakdown_info = {
23
+ method: event.method,
24
+ route: route,
25
+ response_type: event.response_type,
26
+ groups: groups,
27
+ timing: event.duration,
28
+ time: event.time,
29
+ }
30
+
31
+ Celerbrake.notify_performance_breakdown(breakdown_info, stash)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def build_stash
38
+ stash = {}
39
+ request = Celerbrake::Rack::RequestStore[:request]
40
+ return stash unless request
41
+
42
+ stash[:request] = request
43
+ if (user = Celerbrake::Rack::User.extract(request.env))
44
+ stash.merge!(user.as_json)
45
+ end
46
+
47
+ stash
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rails/event'
4
+ require 'celerbrake/rails/app'
5
+
6
+ module Celerbrake
7
+ module Rails
8
+ # ActionControllerRouteSubscriber sends route stat information, including
9
+ # performance data.
10
+ #
11
+ # @since v8.0.0
12
+ class ActionControllerRouteSubscriber
13
+ def call(*args)
14
+ return unless Celerbrake::Config.instance.performance_stats
15
+
16
+ # We don't track routeless events.
17
+ return unless (routes = Celerbrake::Rack::RequestStore[:routes])
18
+
19
+ event = Celerbrake::Rails::Event.new(*args)
20
+ route = Celerbrake::Rails::App.recognize_route(
21
+ Celerbrake::Rack::RequestStore[:request],
22
+ )
23
+ return unless route
24
+
25
+ routes[route.path] = {
26
+ method: event.method,
27
+ response_type: event.response_type,
28
+ groups: {},
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rails
5
+ # Enables support for exceptions occurring in ActiveJob jobs.
6
+ module ActiveJob
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.notify_celerbrake(exception, job)
10
+ notice = Celerbrake.build_notice(exception)
11
+ notice[:context][:component] = 'active_job'
12
+ notice[:context][:action] = job.class.name
13
+ notice[:params].merge!(job.serialize)
14
+
15
+ Celerbrake.notify(notice)
16
+
17
+ raise exception
18
+ end
19
+
20
+ def self.perform(job, block)
21
+ timing = Celerbrake::Benchmark.measure do
22
+ block.call
23
+ end
24
+ rescue StandardError => exception
25
+ Celerbrake.notify_queue(
26
+ queue: job.class.name,
27
+ error_count: 1,
28
+ timing: 0.01,
29
+ )
30
+ raise exception
31
+ else
32
+ Celerbrake.notify_queue(
33
+ queue: job.class.name,
34
+ error_count: 0,
35
+ timing: timing,
36
+ )
37
+ end
38
+
39
+ included do
40
+ rescue_from(Exception) do |exception|
41
+ Celerbrake::Rails::ActiveJob.notify_celerbrake(exception, self)
42
+ end
43
+
44
+ around_perform do |job, block|
45
+ Celerbrake::Rails::ActiveJob.perform(job, block)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rails
5
+ # Rails <4.2 has a bug with regard to swallowing exceptions in the
6
+ # +after_commit+ and the +after_rollback+ hooks: it doesn't bubble up
7
+ # exceptions from there.
8
+ #
9
+ # This module makes it possible to report exceptions occurring there.
10
+ #
11
+ # @see https://github.com/rails/rails/pull/14488 Detailed description of the
12
+ # bug and the fix
13
+ # @see https://goo.gl/348lor Rails 4.2+ implementation (fixed)
14
+ # @see https://goo.gl/ddFNg7 Rails <4.2 implementation (bugged)
15
+ module ActiveRecord
16
+ # Patches default +run_callbacks+ with our version, which is capable of
17
+ # notifying about exceptions.
18
+ #
19
+ # rubocop:disable Lint/RescueException
20
+ def run_callbacks(kind, *args, &block)
21
+ # Let the post process handle the exception if it's not a bugged hook.
22
+ return super unless %i[commit rollback].include?(kind)
23
+
24
+ # Handle the exception ourselves. The 'ex' exception won't be
25
+ # propagated, therefore we must notify it here.
26
+ begin
27
+ super
28
+ rescue Exception => ex
29
+ Celerbrake.notify(ex)
30
+ raise ex
31
+ end
32
+ end
33
+ # rubocop:enable Lint/RescueException
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'celerbrake/rails/event'
4
+ require 'celerbrake/rails/backtrace_cleaner'
5
+
6
+ module Celerbrake
7
+ module Rails
8
+ # ActiveRecordSubscriber sends SQL information, including performance data.
9
+ #
10
+ # @since v8.1.0
11
+ class ActiveRecordSubscriber
12
+ def call(*args)
13
+ return unless Celerbrake::Config.instance.query_stats
14
+
15
+ routes = Celerbrake::Rack::RequestStore[:routes]
16
+ return if !routes || routes.none?
17
+
18
+ event = Celerbrake::Rails::Event.new(*args)
19
+ frame = last_caller
20
+
21
+ routes.each do |route, params|
22
+ Celerbrake.notify_query(
23
+ route: route,
24
+ method: params[:method],
25
+ query: event.sql,
26
+ func: frame[:function],
27
+ file: frame[:file],
28
+ line: frame[:line],
29
+ timing: event.duration,
30
+ time: event.time,
31
+ )
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def last_caller
38
+ exception = StandardError.new
39
+ exception.set_backtrace(
40
+ Celerbrake::Rails::BacktraceCleaner.clean(Kernel.caller),
41
+ )
42
+ Celerbrake::Backtrace.parse(exception).first || {}
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Celerbrake
4
+ module Rails
5
+ # App is a wrapper around Rails.application.
6
+ #
7
+ # @since v9.0.3
8
+ # @api private
9
+ class App
10
+ Route = Struct.new(:path)
11
+
12
+ # @param [] request
13
+ # @return [Celerbrake::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
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/celerbrake/celerbrake/issues/1072
24
+ # * https://github.com/rails/rails/issues/31152
25
+ original_script_name = request.env['SCRIPT_NAME']
26
+
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/celerbrake/celerbrake/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
+
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
66
+ end
67
+
68
+ nil
69
+ end
70
+ # rubocop:enable Metrics/AbcSize
71
+
72
+ def self.engines
73
+ @engines ||= [*::Rails::Engine.subclasses, ::Rails.application]
74
+ end
75
+ private_class_method :engines
76
+ end
77
+ end
78
+ end