Rails アプリケーションのエラー通知

このガイドは、Ruby on Railsアプリケーションで発生する例外(エラー通知)の管理方法を解説します。

本ガイドの内容:

  • RailsのErrorReporterでエラーをキャプチャして通知する方法
  • エラー通知サービス用のカスタムサブスクライバの作成方法

1 エラー通知

RailsのErrorReporterは、アプリケーションで発生した例外を収集して、好みのサービスや場所に通知する標準的な方法を提供します。

このエラーレポーターの目的は、以下のような定型的なエラー処理コードを置き換えることです。

begin
  do_something
rescue SomethingIsBroken => error
  MyErrorReportingService.notify(error)
end

上の定形コードを、以下のようなインターフェイスで統一できます。

Rails.error.handle(SomethingIsBroken) do
  do_something
end

Railsはすべての実行(HTTPリクエスト、ジョブ、rails runnerの起動など)をErrorReporterにラップするので、アプリで発生した未処理のエラーは、そのサブスクライバを介してエラーレポートサービスに自動的に通知されます。

これにより、サードパーティのエラー通知ライブラリは、Rackミドルウェアを挿入したり、未処理の例外をキャプチャするパッチを適用したりする必要がなくなります。また、Active Supportを使うライブラリがこの機能を利用して、従来ログに出力されなかった警告を、コードに手を加えずに通知できるようになります。

このエラーレポーターの利用は必須ではありません。エラーをキャプチャする他の手法はすべて引き続き利用できます。

1.1 エラーレポーターにサブスクライブする

エラーレポーターを利用するにはサブスクライバ(subscriber)が必要です。サブスクライバは、reportメソッドを持つ任意のオブジェクトのことです。アプリケーションでエラーが発生したり、手動で通知されたりすると、Railsのエラーレポーターはエラーオブジェクトといくつかのオプションを使ってこのメソッドを呼び出します。

SentryHoneybadgerなどのように、自動的にサブスクライバを登録してくれるエラー通知ライブラリもあります。詳しくはプロバイダのドキュメントを参照してください。

また、以下のようにカスタムサブスクライバを作成することも可能です。

# 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

Subscriberクラスを定義したら、Rails.error.subscribeメソッドを呼び出して登録します。

Rails.error.subscribe(ErrorSubscriber.new)

サブスクライバはいくつでも登録できます。Railsはサブスクライバを登録順に呼び出します。

Railsのエラーレポーターは、どの環境でも常に登録されたサブスクライバーを呼び出します。しかし多くのエラー通知サービスは、デフォルトではproduction環境でのみエラーを通知します。必要に応じて、複数の環境で設定を行ってテストする必要があります。

1.2 エラーレポーターを利用する

エラーレポーターの使い方は3種類あります。

1.2.1 エラーを通知して握りつぶす

Rails.error.handle は、ブロック内で発生したエラーを通知してから、そのエラーを握りつぶします。ブロックの外の残りのコードは通常通り続行されます。

result = Rails.error.handle do
  1 + '1' # TypeErrorが発生
end
result # => nil
1 + 1 # ここは実行される

ブロック内でエラーが発生しなかった場合、Rails.error.handleはブロックの結果を返し、エラーが発生した場合はnilを返します。

以下のようにfallbackを指定することで、この振る舞いをオーバーライドできます。

user = Rails.error.handle(fallback: -> { User.anonymous }) do
  User.find_by(params[:id])
end
1.2.2 エラーを通知して再度raiseする

Rails.error.record はすべての登録済みレポーターにエラーを通知し、その後エラーを再度raiseします。残りのコードは実行されません。

Rails.error.record do
1 + '1' # TypeErrorが発生
end
1 + 1 # ここは実行されない

ブロック内でエラーが発生しなかった場合、Rails.error.recordはそのブロックの結果を返します。

1.2.3 エラーを手動で通知する

Rails.error.reportを呼び出して手動でエラーを通知することも可能です。

begin
  # code
rescue StandardError => e
  Rails.error.report(e)
end

渡したオプションは、すべてエラーサブスクライバに渡されます。

1.3 エラー通知のオプション

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 エラークラスでフィルタリングする

Rails.error.handleRails.error.recordでは、以下のように特定のクラスのエラーだけを通知できます。

Rails.error.handle(IOError) do
  1 + '1' # TypeErrorが発生
end
1 + 1 # TypeErrorsはIOErrorsではないので、ここは「実行されない」

上のTypeErrorはRailsのエラー通知レポーターにキャプチャされません。通知されるのは IOErrorおよびその子孫インスタンスだけです。その他のエラーは通常どおりraiseします。

1.5 コンテキストをグローバルに設定する

コンテキストは、contextオプションで設定することも、以下のように#set_contextAPIで設定することもできます。

Rails.error.set_context(section: "checkout", user_id: @user.id)

この方法で設定されたコンテキストは、contextオプションとマージされます。

Rails.error.set_context(a: 1)
Rails.error.handle(context: { b: 2 }) { raise }
# 通知されるコンテキスト: {:a=>1, :b=>2}
Rails.error.handle(context: { b: 3 }) { raise }
# 通知されるコンテキスト: {:a=>1, :b=>3}

1.6 ライブラリで利用する

エラー通知ライブラリは、以下のようにRailtieでライブラリのサブスクライバを登録できます。

module MySdk
  class Railtie < ::Rails::Railtie
    initializer "my_sdk.error_subscribe" do
      Rails.error.subscribe(MyErrorSubscriber.new)
    end
  end
end

エラーサブスクライバを登録すると、Rackミドルウェアのような他のエラー機構がある場合、エラーが何度も通知される可能性があります。他のエラー機構を削除するか、レポーターの機能を調整して、通知済みの例外を通知しないようにする必要があります。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加