更多内容请访问 rubyonrails.org:

Rails 应用程序中的错误报告

本指南介绍了在 Rails 应用程序中管理错误的方法。

阅读本指南后,您将了解

  • 如何使用 Rails 的错误报告器来捕获和报告错误。
  • 如何为您的错误报告服务创建自定义订阅者。

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 来注销订阅者。这在您希望替换或删除依赖项之一添加的订阅者时可能很有用。subscribeunsubscribe 都可以接受订阅者或类,如下所示

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.handleRails.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来阻止订阅者在某个代码块期间收到错误通知。与subscribeunsubscribe类似,您可以传入订阅者本身或其类。

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 中间件),您可能会遇到错误被多次报告的情况。您应该删除其他机制,或者调整您的报告功能,使其跳过已报告过的错误。



返回顶部