1 错误报告
Rails 错误报告器 提供了一种标准方法来收集应用程序中发生的错误,并将它们报告到您首选的服务或位置(例如,您可以将错误报告到监控服务,如 Sentry)。
它旨在替换如下类似的样板错误处理代码
begin
do_something
rescue SomethingIsBroken => error
MyErrorReportingService.notify(error)
end
使用一致的接口
Rails.error.handle(SomethingIsBroken) do
do_something
end
Rails 将所有执行(如 HTTP 请求、作业 和 rails runner 调用)包装在错误报告器中,因此应用程序中任何未处理的错误都将通过其订阅者自动报告到您的错误报告服务。
这意味着第三方错误报告库不再需要插入 Rack 中间件或进行任何猴子补丁来捕获未处理的错误。使用 Active Support 的库也可以使用它来非侵入性地报告以前在日志中丢失的警告。
使用 Rails 错误报告器是可选的,因为其他捕获错误的方法仍然有效。
1.1 订阅报告器
要将错误报告器与外部服务一起使用,您需要一个订阅者。订阅者可以是任何具有 report
方法的 Ruby 对象。当应用程序中发生错误或手动报告错误时,Rails 错误报告器将使用错误对象和一些选项调用此方法。
一些错误报告库,如 Sentry 和 Honeybadger 的库,会自动为您注册订阅者。
您也可以创建自定义订阅者。例如
# config/initializers/error_subscriber.rb
class ErrorSubscriber
def report(error, handled:, severity:, context:, source: nil)
MyErrorReportingService.report_error(error, context: context, handled: handled, level: severity)
end
end
定义完订阅者类后,您可以通过调用 Rails.error.subscribe
方法来注册它
Rails.error.subscribe(ErrorSubscriber.new)
您可以注册任意数量的订阅者。Rails 将按注册顺序调用它们。
也可以通过调用 Rails.error.unsubscribe
来注销订阅者。这在您希望替换或删除依赖项之一添加的订阅者时可能很有用。subscribe
和 unsubscribe
都可以接受订阅者或类,如下所示
subscriber = ErrorSubscriber.new
Rails.error.unsubscribe(subscriber)
# or
Rails.error.unsubscribe(ErrorSubscriber)
无论您的环境如何,Rails 错误报告器都会始终调用注册的订阅者。但是,许多错误报告服务默认情况下只报告生产环境中的错误。您应该根据需要在不同环境中配置和测试您的设置。
1.2 使用错误报告器
Rails 错误报告器有四种方法,允许您以不同方式报告方法
Rails.error.handle
Rails.error.record
Rails.error.report
Rails.error.unexpected
1.2.1 报告和吞并错误
Rails.error.handle
方法将报告块中引发的任何错误。然后它将吞并错误,块外部的代码将照常继续执行。
result = Rails.error.handle do
1 + "1" # raises TypeError
end
result # => nil
1 + 1 # This will be executed
如果块中没有引发错误,Rails.error.handle
将返回块的结果,否则将返回 nil
。您可以通过提供一个 fallback
来覆盖它
user = Rails.error.handle(fallback: -> { User.anonymous }) do
User.find(params[:id])
end
1.2.2 报告和重新引发错误
Rails.error.record
方法将错误报告给所有注册的订阅者,然后重新引发错误,这意味着您的代码的其余部分将不会执行。
Rails.error.record do
1 + "1" # raises TypeError
end
1 + 1 # This won't be executed
如果块中没有引发错误,Rails.error.record
将返回块的结果。
1.2.3 手动报告错误
您也可以通过调用 Rails.error.report
手动报告错误
begin
# code
rescue StandardError => e
Rails.error.report(e)
end
您传递的任何选项都将传递给错误订阅者。
1.2.4 报告意外错误
您可以通过调用 Rails.error.unexpected
来报告任何意外错误。
在生产环境中调用时,此方法将在报告错误后返回 nil,并且您的代码将继续执行。
在开发环境中调用时,错误将包装在一个新的错误类中(以确保它不会在堆栈中更高的地方被救援)并显示给开发人员进行调试。
例如
def edit
if published?
Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
false
end
# ...
end
此方法旨在优雅地处理生产环境中可能发生的任何错误,但这些错误预计不会是典型使用结果。
1.3 错误报告选项
报告 API #handle
、#record
和 #report
支持以下选项,这些选项随后将传递给所有注册的订阅者
handled
:一个Boolean
,指示错误是否已处理。默认情况下设置为true
。#record
将其设置为false
。severity
:一个Symbol
,描述错误的严重程度。预期值为::error
、:warning
和:info
。#handle
将其设置为:warning
,而#record
将其设置为:error
。context
:一个Hash
,提供有关错误的更多上下文,例如请求或用户详细信息source
:一个String
,关于错误的来源。默认来源是"application"
。内部库报告的错误可能会设置其他来源;例如,Redis 缓存库可能会使用"redis_cache_store.active_support"
。您的订阅者可以使用来源来忽略您不感兴趣的错误。
Rails.error.handle(context: { user_id: user.id }, severity: :info) do
# ...
end
1.4 全局设置上下文
除了通过 context
选项设置上下文之外,您还可以使用 Rails.error.set_context
。例如
Rails.error.set_context(section: "checkout", user_id: @user.id)
通过这种方式设置的任何上下文都将与 context
选项合并
Rails.error.set_context(a: 1)
Rails.error.handle(context: { b: 2 }) { raise }
# The reported context will be: {:a=>1, :b=>2}
Rails.error.handle(context: { b: 3 }) { raise }
# The reported context will be: {:a=>1, :b=>3}
1.5 按错误类过滤
使用Rails.error.handle
和Rails.error.record
,您也可以选择只报告某些类别的错误。例如
Rails.error.handle(IOError) do
1 + "1" # raises TypeError
end
1 + 1 # TypeErrors are not IOErrors, so this will *not* be executed
这里,TypeError
不会被 Rails 错误报告器捕获。只有IOError
及其子类的实例才会被报告。任何其他错误将被正常抛出。
1.6 禁用通知
您可以通过调用Rails.error.disable
来阻止订阅者在某个代码块期间收到错误通知。与subscribe
和unsubscribe
类似,您可以传入订阅者本身或其类。
Rails.error.disable(ErrorSubscriber) do
1 + "1" # TypeError will not be reported via the ErrorSubscriber
end
这对于第三方错误报告服务也很有用,它们可能希望以不同的方式或在更高级别上管理错误处理。
2 错误报告库
错误报告库可以在Railtie中注册它们的订阅者
module MySdk
class Railtie < ::Rails::Railtie
initializer "my_sdk.error_subscribe" do
Rails.error.subscribe(MyErrorSubscriber.new)
end
end
end
如果您注册了一个错误订阅者,但仍有其他错误机制(如 Rack 中间件),您可能会遇到错误被多次报告的情况。您应该删除其他机制,或者调整您的报告功能,使其跳过已报告过的错误。