更多内容请访问 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 调用)都包装在错误报告器中,因此应用程序中引发的任何未处理错误都将通过其订阅者自动报告给您的错误报告服务。

对于 HTTP 请求,ActionDispatch::ExceptionWrapper.rescue_responses 中存在的错误不会被报告,因为它们不会导致服务器错误 (500),并且通常不是需要解决的错误。

这意味着第三方错误报告库不再需要插入 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 中间件,您可能会导致错误被报告多次。您应该删除其他机制,或者调整您的报告功能,使其跳过报告以前已经见过的错误。



回到顶部