ActiveSupport::ErrorReporter#report assigns a backtrace to unraised exceptions

Previously reporting an un-raised exception would result in an error report without
a backtrace. Now it automatically generates one.
This commit is contained in:
Jean Boussier 2024-08-23 10:16:01 +02:00
parent ca05b17c99
commit b1d8cf59d9
3 changed files with 43 additions and 1 deletions

View File

@ -1,3 +1,10 @@
* `ActiveSupport::ErrorReporter#report` now assigns a backtrace to unraised exceptions.
Previously reporting an un-raised exception would result in an error report without
a backtrace. Now it automatically generates one.
*Jean Boussier*
* Add `escape_html_entities` option to `ActiveSupport::JSON.encode`.
This allows for overriding the global configuration found at

View File

@ -144,9 +144,9 @@ module ActiveSupport
#
def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
error = RuntimeError.new(error) if error.is_a?(String)
error.set_backtrace(caller(1)) if error.backtrace.nil?
if @debug_mode
ensure_backtrace(error)
raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
else
report(error, handled: true, severity: severity, context: context, source: source)
@ -209,6 +209,7 @@ module ActiveSupport
#
def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
return if error.instance_variable_defined?(:@__rails_error_reported)
ensure_backtrace(error)
unless SEVERITIES.include?(severity)
raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
@ -237,5 +238,28 @@ module ActiveSupport
nil
end
private
def ensure_backtrace(error)
return if error.frozen? # re-raising won't add a backtrace
return unless error.backtrace.nil?
begin
# We could use Exception#set_backtrace, but until Ruby 3.4
# it only support setting `Exception#backtrace` and not
# `Exception#backtrace_locations`. So raising the exception
# is a good way to build a real backtrace.
raise error
rescue error.class => error
end
count = 0
while error.backtrace_locations.first&.path == __FILE__
count += 1
error.backtrace_locations.shift
end
error.backtrace.shift(count)
end
end
end

View File

@ -161,6 +161,16 @@ class ErrorReporterTest < ActiveSupport::TestCase
assert_equal [[error, false, :error, "application", {}]], @subscriber.events
end
test "#report assigns a backtrace if it's missing" do
error = RuntimeError.new("Oops")
assert_nil error.backtrace
assert_nil error.backtrace_locations
assert_nil @reporter.report(error)
assert_not_predicate error.backtrace, :empty?
assert_not_predicate error.backtrace_locations, :empty?
end
test "#record passes through the return value" do
result = @reporter.record do
2 + 2
@ -173,6 +183,7 @@ class ErrorReporterTest < ActiveSupport::TestCase
assert_nil @reporter.unexpected(error)
assert_equal [[error, true, :warning, "application", {}]], @subscriber.events
assert_not_predicate error.backtrace, :empty?
assert_not_predicate error.backtrace_locations, :empty?
end
test "#unexpected accepts an error message" do