Skip to content

Commit

Permalink
ErrorReporter#debug to swallow in production but raise in development
Browse files Browse the repository at this point in the history
It's a common useful pattern for situation where something isn't
supposed to fail, but if it does we can recover from it.

So in such situation you don't want such failure to be swallowed
in development or test, as it's likely a bug, but do not want to
fail a request if it happens in production.

In other words, it behaves like `#record` in development and test
environments, and like `handle` in production.

Fix: rails#49638
Fix: rails#49339

Co-Authored-By: Andrew Novoselac <[email protected]>
Co-Authored-By: Dustin Brown <[email protected]>
  • Loading branch information
3 people committed Nov 9, 2023
1 parent 60d05cd commit 36bca21
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 4 deletions.
26 changes: 23 additions & 3 deletions activesupport/lib/active_support/error_reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ module ActiveSupport
class ErrorReporter
SEVERITIES = %i(error warning info)
DEFAULT_SOURCE = "application"
DEFAULT_RESCUE = [StandardError].freeze

attr_accessor :logger
attr_accessor :logger, :debug_mode

def initialize(*subscribers, logger: nil)
@subscribers = subscribers.flatten
@logger = logger
@debug_mode = false
end

# Evaluates the given block, reporting and swallowing any unhandled error.
Expand Down Expand Up @@ -72,7 +74,7 @@ def initialize(*subscribers, logger: nil)
# source of the error. Subscribers can use this value to ignore certain
# errors. Defaults to <tt>"application"</tt>.
def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
error_classes = [StandardError] if error_classes.blank?
error_classes = DEFAULT_RESCUE if error_classes.empty?
yield
rescue *error_classes => error
report(error, handled: true, severity: severity, context: context, source: source)
Expand Down Expand Up @@ -108,13 +110,31 @@ def handle(*error_classes, severity: :warning, context: {}, fallback: nil, sourc
# source of the error. Subscribers can use this value to ignore certain
# errors. Defaults to <tt>"application"</tt>.
def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
error_classes = [StandardError] if error_classes.blank?
error_classes = DEFAULT_RESCUE if error_classes.empty?
yield
rescue *error_classes => error
report(error, handled: false, severity: severity, context: context, source: source)
raise
end

# Evaluates the given block, reporting any unhandled error.
# The error is then either re-raised if +config.consider_all_requests_local == true+
# or otherwise swallowed.
#
# In other words in behaves like +record+ in development and test, and like +handle+
# in production.
def debug(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
error_classes = DEFAULT_RESCUE if error_classes.empty?
yield
rescue *error_classes => error
report(error, handled: true, severity: severity, context: context, source: source)
if @debug_mode
raise
else
fallback.call if fallback
end
end

# Register a new error subscriber. The subscriber must respond to
#
# report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)
Expand Down
17 changes: 17 additions & 0 deletions activesupport/test/error_reporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,23 @@ class ErrorReporterTest < ActiveSupport::TestCase
assert_equal 4, result
end

test "#debug swallows errors by default" do
result = @reporter.debug(fallback: -> { 2 + 2 }) do
raise StandardError
end
assert_equal 4, result
end

test "#debug re-raise errors in development and test" do
@reporter.debug_mode = true
error = assert_raises RumtimeError do
@reporter.debug(fallback: -> { nil }) do
raise "Oops"
end
end
assert_includes error.message, "Oops"
end

test "can have multiple subscribers" do
second_subscriber = ErrorSubscriber.new
@reporter.subscribe(second_subscriber)
Expand Down
6 changes: 5 additions & 1 deletion railties/lib/rails/application/bootstrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ module Bootstrap
broadcast_logger.formatter = Rails.logger.formatter
Rails.logger = broadcast_logger
end
end

unless config.consider_all_requests_local
initializer :initialize_error_reporter, group: :all do
if config.consider_all_requests_local
Rails.error.debug_mode = true
else
Rails.error.logger = Rails.logger
end
end
Expand Down

0 comments on commit 36bca21

Please sign in to comment.