更多資訊請參閱 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 中介軟體或進行任何 monkey-patching 來捕獲未處理的錯誤。使用 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 支援下列選項,這些選項接著會傳遞給所有已註冊的訂閱者

  • handledBoolean 值,指出錯誤是否已處理。預設設為 true#record 將此值設為 false
  • severitySymbol 值,描述錯誤的嚴重性。預期值為::error:warning:info#handle 將此值設為 :warning,而 #record 將其設為 :error
  • contextHash 值,用於提供更多有關錯誤的上下文,例如請求或使用者詳細資訊
  • sourceString 值,說明錯誤的來源。預設來源為 "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

在此範例中,Rails 錯誤回報器將不會捕獲 TypeError。只會回報 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 中介軟體),您最終可能會多次回報錯誤。您應該移除其他機制,或調整回報功能,使其跳過回報之前已看到的錯誤。



返回頁首