Allow ErrorReporter to handle several error classes

`Rails.error.handle` and `Rails.error.record` are able to filter by list of serveral error classes now, for example like this:

```ruby
Rails.error.handle(ArgumentError, TypeError) do
  [1, 2, 3].first(x) # where `x` might be `-4` or `'4'`
end
```
This commit is contained in:
Martin Spickermann 2022-10-21 12:50:48 +02:00
parent b96ddea5f0
commit 1a9b887c0c
4 changed files with 58 additions and 9 deletions

View File

@ -1,3 +1,14 @@
* `Rails.error.handle` and `Rails.error.record` filter now by multiple error classes.
```ruby
Rails.error.handle(IOError, ArgumentError) do
1 + '1' # raises TypeError
end
1 + 1 # TypeErrors are not IOErrors or ArgumentError, so this will *not* be handled
```
*Martin Spickermann*
* `Class#subclasses` and `Class#descendants` now automatically filter reloaded classes. * `Class#subclasses` and `Class#descendants` now automatically filter reloaded classes.
Previously they could return old implementations of reloadable classes that have been Previously they could return old implementations of reloadable classes that have been

View File

@ -42,7 +42,7 @@ module ActiveSupport
# 1 + '1' # 1 + '1'
# end # end
# #
# Can be restricted to handle only a specific error class: # Can be restricted to handle only specific error classes:
# #
# maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") } # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
# #
@ -69,9 +69,10 @@ module ActiveSupport
# * +:source+ - This value is passed along to subscribers to indicate the # * +:source+ - This value is passed along to subscribers to indicate the
# source of the error. Subscribers can use this value to ignore certain # source of the error. Subscribers can use this value to ignore certain
# errors. Defaults to <tt>"application"</tt>. # errors. Defaults to <tt>"application"</tt>.
def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE) def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
error_classes = [StandardError] if error_classes.blank?
yield yield
rescue error_class => error rescue *error_classes => error
report(error, handled: true, severity: severity, context: context, source: source) report(error, handled: true, severity: severity, context: context, source: source)
fallback.call if fallback fallback.call if fallback
end end
@ -84,7 +85,7 @@ module ActiveSupport
# 1 + '1' # 1 + '1'
# end # end
# #
# Can be restricted to handle only a specific error class: # Can be restricted to handle only specific error classes:
# #
# tags = Rails.error.record(Redis::BaseError) { redis.get("tags") } # tags = Rails.error.record(Redis::BaseError) { redis.get("tags") }
# #
@ -104,9 +105,10 @@ module ActiveSupport
# * +:source+ - This value is passed along to subscribers to indicate the # * +:source+ - This value is passed along to subscribers to indicate the
# source of the error. Subscribers can use this value to ignore certain # source of the error. Subscribers can use this value to ignore certain
# errors. Defaults to <tt>"application"</tt>. # errors. Defaults to <tt>"application"</tt>.
def record(error_class = StandardError, severity: :error, context: {}, source: DEFAULT_SOURCE) def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
error_classes = [StandardError] if error_classes.blank?
yield yield
rescue error_class => error rescue *error_classes => error
report(error, handled: false, severity: severity, context: context, source: source) report(error, handled: false, severity: severity, context: context, source: source)
raise raise
end end

View File

@ -68,6 +68,23 @@ class ErrorReporterTest < ActiveSupport::TestCase
assert_equal [], @subscriber.events assert_equal [], @subscriber.events
end end
test "#handle can be scoped to several exception classes" do
assert_raises ArgumentError do
@reporter.handle(NameError, NoMethodError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#handle swallows and reports matching errors" do
error = ArgumentError.new("Oops")
@reporter.handle(NameError, ArgumentError) do
raise error
end
assert_equal [[error, true, :warning, "application", {}]], @subscriber.events
end
test "#handle passes through the return value" do test "#handle passes through the return value" do
result = @reporter.handle do result = @reporter.handle do
2 + 2 2 + 2
@ -125,6 +142,25 @@ class ErrorReporterTest < ActiveSupport::TestCase
assert_equal [], @subscriber.events assert_equal [], @subscriber.events
end end
test "#record can be scoped to several exception classes" do
assert_raises ArgumentError do
@reporter.record(NameError, NoMethodError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#record report any matching, unhandled error and re-raise them" do
error = ArgumentError.new("Oops")
assert_raises ArgumentError do
@reporter.record(NameError, ArgumentError) do
raise error
end
end
assert_equal [[error, false, :error, "application", {}]], @subscriber.events
end
test "#record passes through the return value" do test "#record passes through the return value" do
result = @reporter.record do result = @reporter.record do
2 + 2 2 + 2

View File

@ -131,9 +131,9 @@ Rails.error.handle(context: {user_id: user.id}, severity: :info) do
end end
``` ```
### Filtering by Error Class ### Filtering by Error Classes
With `Rails.error.handle` and `Rails.error.record`, you can also choose to only report errors of a certain class. For example: With `Rails.error.handle` and `Rails.error.record`, you can also choose to only report errors of certain classes. For example:
```ruby ```ruby
Rails.error.handle(IOError) do Rails.error.handle(IOError) do
@ -176,4 +176,4 @@ module MySdk
end end
``` ```
If you register an error subscriber, but still have other error mechanisms like a Rack middleware, you may end up with errors reported multiple times. You should either remove your other mechanisms or adjust your report functionality so it skips reporting an exception it has seen before. If you register an error subscriber, but still have other error mechanisms like a Rack middleware, you may end up with errors reported multiple times. You should either remove your other mechanisms or adjust your report functionality so it skips reporting an exception it has seen before.