rescue_from_handledイベントに完全なバックトレースを格納
Rails 8.2では、rescue_from_handled.action_controller通知のペイロードに完全なバックトレースを含められるようになりました。config.action_controller.rescue_from_event_backtraceを:arrayに設定することで、例外の全スタックトレースを取得できます。
背景
Rails 8.1で導入されたrescue_from_handled.action_controllerイベントは、#56060で指摘されたように、exception_backtraceの扱いに一貫性の問題がありました。active_job.completedイベントが完全なバックトレースオブジェクトを提供するのに対し、rescue_from_handled.action_controllerイベントはバックトレースの最初のフレームのみを文字列として提供していました。
この不一致は、可観測性ツールがイベントペイロードを処理する際に混乱を招く可能性があります。異なるイベント間でexception_backtraceの型が異なるため、統一的なエラートラッキングの実装が困難でした。
技術的な変更
ActionController::StructuredEventSubscriberに新しいクラス属性_rescue_from_event_backtraceが追加され、バックトレースの形式を制御できるようになりました。
変更前:
def rescue_from_callback(event)
exception = event.payload[:exception]
exception_backtrace = exception.backtrace&.first
exception_backtrace = exception_backtrace&.delete_prefix("#{Rails.root}/") if defined?(Rails.root) && Rails.root
emit_event("action_controller.rescue_from_handled",
exception_class: exception.class.name,
exception_message: exception.message,
exception_backtrace: exception_backtrace
)
end
変更後:
def rescue_from_callback(event)
exception = event.payload[:exception]
if self.class._rescue_from_event_backtrace == :array
exception_backtrace = exception.backtrace
else
exception_backtrace = exception.backtrace&.first
exception_backtrace = exception_backtrace&.delete_prefix("#{Rails.root}/") if defined?(Rails.root) && Rails.root
end
emit_event("action_controller.rescue_from_handled",
exception_class: exception.class.name,
exception_message: exception.message,
exception_backtrace: exception_backtrace
)
end
設定値はrailties/lib/rails/application/configuration.rbで、Rails 8.2のデフォルト値として:arrayが指定されます。この設定はaction_controller.structured_event_subscriberイニシャライザを通じてActionController::StructuredEventSubscriberに渡されます。
ActionController::LogSubscriberも更新され、配列形式のバックトレースを受け取った場合は最初の要素を取り出してログに出力します:
def rescue_from_handled(event)
exception_class = event[:payload][:exception_class]
exception_message = event[:payload][:exception_message]
exception_backtrace = event[:payload][:exception_backtrace]
if exception_backtrace.is_a?(Array)
exception_backtrace = exception_backtrace&.first
exception_backtrace = exception_backtrace&.delete_prefix("#{Rails.root}/") if defined?(Rails.root) && Rails.root
end
info { "rescue_from handled #{exception_class} (#{exception_message}) - #{exception_backtrace}" }
end
この実装により、イベントペイロードには完全なバックトレースが含まれる一方、ログ出力は従来どおり最初のフレームのみを表示します。
設計判断
既存の動作を保持しながら新しいオプションを追加する方式が採用されました。PR内の議論では、Rails 8.1で導入されたばかりのイベントを即座に変更するか、設定フラグと非推奨化プロセスを経るかが検討されました。
最終的に選ばれたアプローチは、config.action_controller.rescue_from_event_backtraceという設定項目を導入し、Rails 8.2では:arrayをデフォルトにするというものです。nilの場合は従来の動作(最初のフレームのみ)を維持し、:arrayの場合は完全なバックトレースを提供します。
この判断により、Rails 8.1ですでにこのイベントを利用している可観測性ツールに対して、CHANGELOGと設定フラグという明確な移行パスを提供しています。イベント自体が新しいため、即座に変更することも可能でしたが、より慎重なアプローチが選ばれました。
まとめ
本PRは、rescue_from_handled.action_controllerイベントのペイロードに完全なバックトレースを含められるようにした変更です。設定可能な形式にすることで、既存のイベントリスナーとの互換性を保ちながら、Active Jobイベントとの一貫性を確保しています。Rails 8.2では:arrayがデフォルトとなり、より詳細なエラートラッキングが標準で利用可能になります。