diff --git a/lib/logtail-rails/error_event.rb b/lib/logtail-rails/error_event.rb index 3755ee2..7f37df7 100755 --- a/lib/logtail-rails/error_event.rb +++ b/lib/logtail-rails/error_event.rb @@ -18,9 +18,11 @@ class ErrorEvent < Logtail::Integrations::Rack::Middleware # We determine this when the app loads to avoid the overhead on a per request basis. EXCEPTION_WRAPPER_TAKES_CLEANER = defined?(::ActionDispatch::ExceptionWrapper) && !::ActionDispatch::ExceptionWrapper.instance_methods.include?(:env) - # config.action_dispatch.log_rescued_responses (Rails 7.0+), as ActionDispatch::DebugExceptions - # reads it. This gem silences its logging and logs the exception here instead. + # config.action_dispatch.log_rescued_responses (Rails 7.0+) and + # config.action_dispatch.debug_exception_log_level (Rails 7.1+), as ActionDispatch::DebugExceptions + # reads them. This gem silences its logging and logs the exception here instead. LOG_RESCUED_RESPONSES_KEY = "action_dispatch.log_rescued_responses".freeze + DEBUG_EXCEPTION_LOG_LEVEL_KEY = "action_dispatch.debug_exception_log_level".freeze def call(env) begin @@ -38,7 +40,7 @@ def call(env) def log_exception(env, exception) return if !log_exception?(env, exception) - Config.instance.logger.fatal do + Config.instance.logger.add(env[DEBUG_EXCEPTION_LOG_LEVEL_KEY] || ::Logger::FATAL) do backtrace = extract_backtrace(env, exception) Events::Error.new( name: exception.class.name, diff --git a/spec/logtail-rails/error_event_spec.rb b/spec/logtail-rails/error_event_spec.rb index af9cfe1..cc58666 100755 --- a/spec/logtail-rails/error_event_spec.rb +++ b/spec/logtail-rails/error_event_spec.rb @@ -139,14 +139,45 @@ def method_for_action(action_name) expect(response_statuses).to eq([404, 500]) end - it "should log the error at fatal even when config.action_dispatch.debug_exception_log_level is :error" do + it "should log the error at config.action_dispatch.debug_exception_log_level, :error with load_defaults 7.1 or later" do skip("config.action_dispatch.debug_exception_log_level is new in Rails 7.1") unless ::Rails.application.env_config.key?("action_dispatch.debug_exception_log_level") dispatch_rendering_exceptions("/runtime_error", "action_dispatch.debug_exception_log_level" => ::Logger::ERROR) + expect(error_rows.map { |row| [row["level"], row["message"]] }).to eq([["error", "RuntimeError (Boom!)"]]) + expect(response_statuses).to eq([500]) + end + + it "should log every error, rescued responses too, at warn when config.action_dispatch.debug_exception_log_level is :warn" do + skip("config.action_dispatch.debug_exception_log_level is new in Rails 7.1") unless ::Rails.application.env_config.key?("action_dispatch.debug_exception_log_level") + + dispatch_rendering_exceptions("/runtime_error", "action_dispatch.debug_exception_log_level" => ::Logger::WARN) + dispatch_rendering_exceptions("/record_not_found", "action_dispatch.debug_exception_log_level" => ::Logger::WARN) + + expect(error_rows.map { |row| [row["level"], row["message"]] }).to eq([["warn", "RuntimeError (Boom!)"], ["warn", "ActiveRecord::RecordNotFound (Couldn't find User)"]]) + expect(response_statuses).to eq([500, 404]) + end + + it "should log the error at fatal, Rails' default, when the app doesn't set config.action_dispatch.debug_exception_log_level" do + skip("config.action_dispatch.debug_exception_log_level is new in Rails 7.1") unless ::Rails.application.env_config.key?("action_dispatch.debug_exception_log_level") + + # The spec app neither sets it nor loads the 7.1 defaults, so it has the railtie's :fatal + expect(::Rails.application.env_config["action_dispatch.debug_exception_log_level"]).to eq(::Logger::FATAL) + + dispatch_rendering_exceptions("/runtime_error") + expect(error_rows.map { |row| [row["level"], row["message"]] }).to eq([["fatal", "RuntimeError (Boom!)"]]) end + it "should log every error at fatal on Rails versions without config.action_dispatch.debug_exception_log_level" do + skip("config.action_dispatch.debug_exception_log_level exists since Rails 7.1") if ::Rails.application.env_config.key?("action_dispatch.debug_exception_log_level") + + dispatch_rendering_exceptions("/runtime_error") + dispatch_rendering_exceptions("/record_not_found") + + expect(error_rows.map { |row| [row["level"], row["message"]] }).to eq([["fatal", "RuntimeError (Boom!)"], ["fatal", "ActiveRecord::RecordNotFound (Couldn't find User)"]]) + end + # Renders exceptions as error pages like a production app, instead of raising them def dispatch_rendering_exceptions(path, env_config = {}) show_exceptions = ::Rails.gem_version >= Gem::Version.new("7.1") ? :all : true