diff --git a/sentry-rails/lib/sentry/rails/railtie.rb b/sentry-rails/lib/sentry/rails/railtie.rb index d997e5c46..c6798904f 100644 --- a/sentry-rails/lib/sentry/rails/railtie.rb +++ b/sentry-rails/lib/sentry/rails/railtie.rb @@ -6,6 +6,11 @@ module Sentry class Railtie < ::Rails::Railtie + # Referencing ActionController::Live loads ActionDispatch::Response, and Rails + # warns when that happens before the application has initialized. Rails 8.1.4 + # added the :action_controller_live load hook so we can wait for it instead. + SUPPORTS_ACTION_CONTROLLER_LIVE_LOAD_HOOK = Gem::Version.new(::Rails.version) >= Gem::Version.new("8.1.4") + # middlewares can't be injected after initialize initializer "sentry.use_rack_middleware" do |app| # placed after all the file-sending middlewares so we can avoid unnecessary transactions @@ -99,7 +104,16 @@ def extend_controller_methods ActiveSupport.on_load :action_controller do include Sentry::Rails::ControllerMethods include Sentry::Rails::ControllerTransaction - ActionController::Live.send(:prepend, Sentry::Rails::Overrides::StreamingReporter) + end + + if SUPPORTS_ACTION_CONTROLLER_LIVE_LOAD_HOOK + ActiveSupport.on_load :action_controller_live do + prepend Sentry::Rails::Overrides::StreamingReporter + end + else + ActiveSupport.on_load :action_controller do + ActionController::Live.send(:prepend, Sentry::Rails::Overrides::StreamingReporter) + end end end diff --git a/sentry-rails/spec/isolated/streaming_reporter_spec.rb b/sentry-rails/spec/isolated/streaming_reporter_spec.rb new file mode 100644 index 000000000..61b25354f --- /dev/null +++ b/sentry-rails/spec/isolated/streaming_reporter_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +begin + require "simplecov" + SimpleCov.command_name "StreamingReporter" +rescue LoadError +end + +require "shellwords" +require "sentry/rails/overrides/streaming_reporter" + +RSpec.describe Sentry::Rails::Overrides::StreamingReporter do + it "is prepended to ActionController::Live" do + make_basic_app + + expect(ActionController::Live.ancestors).to include(described_class) + end + + # rspec-rails loads ActionController::Live before the suite starts, so boot a + # bare app in a child process to see what initialization alone loads. + it "doesn't load ActionController::Live while the app initializes" do + skip "Rails #{::Rails.version} has no :action_controller_live load hook" unless Sentry::Railtie::SUPPORTS_ACTION_CONTROLLER_LIVE_LOAD_HOOK + + script = <<~RUBY + require "action_controller/railtie" + require "sentry-rails" + + class LiveLoadApp < Rails::Application + config.eager_load = false + config.secret_key_base = "secret" + end + + LiveLoadApp.initializer :sentry do + Sentry.init do |config| + config.dsn = "http://12345:67890@sentry.localdomain:3000/sentry/42" + config.sdk_logger = Logger.new(nil) + config.background_worker_threads = 0 + end + end + + LiveLoadApp.initialize! + # loading controllers must not drag ActionController::Live in with them + ActionController::Base + + print ActionController.autoload?(:Live) ? "lazy" : "loaded" + RUBY + + expect(`#{RbConfig.ruby} -e #{Shellwords.escape(script)}`.lines.last).to eq("lazy") + end +end