Evented LogSubscriber を Ractor 安全な共有可能に変更
EventReporter の内部で使用されている LogSubscriber がプロックを保持したままでは Ractor 間で安全に共有できません。本 PR はそのプロック保持を排除し、動作を変えずに Ractor 安全な実装へとリファクタリングします。
背景
LogSubscriber は LEVEL_CHECKS というハッシュに debug, info, error 各レベル用のプロックを格納していました。これらのプロックは ActiveSupport::Ractors.shareable_lambda で共有可能にしていたものの、プロック自体が Ractor の境界を越える際に重いオーバーヘッドと可読性の低下を招いていました。Ractor 安全性を確保するためには、定数にプロックを残さずシンプルなデータだけを保持する設計が求められます。本変更はその要件を満たすことを目的としています。
技術的な変更
LogSubscriber の定数置き換え
変更前 は LEVEL_CHECKS ハッシュに ActiveSupport::Ractors.shareable_lambda でラップしたプロックが格納されていました。
-LEVEL_CHECKS = {
- debug: ActiveSupport::Ractors.shareable_lambda(&-> (logger) { logger.debug? }),
- info: ActiveSupport::Ractors.shareable_lambda(&-> (logger) { logger.info? }),
- error: ActiveSupport::Ractors.shareable_lambda(&-> (logger) { logger.error? })
-}.freeze
+LOG_LEVELS = [:debug, :info, :error].freeze
変更後 はレベル名だけを配列 LOG_LEVELS として保持し、実際の判定は log_level_satisfied? メソッドで行います。これにより定数は純粋なデータ構造のみとなり、Ractor 間で安全に共有できます。
emit メソッドの判定ロジック更新
emit での呼び出しは従来 LEVEL_CHECKS[log_levels[event_method]]&.call(logger) で行われていましたが、以下のように簡潔に置き換えられました。
-public_send(event_method, event) if LEVEL_CHECKS[log_levels[event_method]]&.call(logger)
+public_send(event_method, event) if log_level_satisfied?(event_method)
ログレベル判定ヘルパーの追加
新たに log_level_satisfied?(event_method) を実装し、現在のロガーが対象レベルを許容しているかを直接問い合わせます。
+def log_level_satisfied?(event_method)
+ event_log_level = log_levels[event_method]
+ return false unless LOG_LEVELS.include?(event_log_level)
+ logger.public_send("#{event_log_level}?")
+end
この実装は LOG_LEVELS に含まれないレベルが指定された場合は常に false を返すことで安全性を確保します。
StructuredEventSubscriber のデバッグチェック簡素化
StructuredEventSubscriber でも同様にプロックを排除し、DEBUG_CHECK 定数を削除して silenced_events のハッシュ値を true に置き換えました。
@@ -31,8 +31,6 @@
- DEBUG_CHECK = ActiveSupport::Ractors.shareable_proc { !ActiveSupport.event_reporter.debug_mode? }
@@
- subscriber.silenced_events = debug_methods.to_h { |method| ["#{method}.#{namespace}", DEBUG_CHECK] }
+ subscriber.silenced_events = debug_methods.to_h { |method| ["#{method}.#{namespace}", true] }
@@
- ActiveSupport.event_reporter.subscribers.none? || @silenced_events[event]&.call
+ ActiveSupport.event_reporter.subscribers.none? || (@silenced_events.key?(event) && !ActiveSupport.event_reporter.debug_mode?)
silenced? のロジックもプロック呼び出しを排除し、debug_mode? の反転チェックに変更しました。これにより同様に Ractor 安全な構造となります。
設計判断
本リファクタリングは プロックを定数に保持しない という方針で実装されています。shareable_lambda や shareable_proc を使用すると可読性が低下し、Ractor 境界でのコピーコストが増大しますが、配列と直接的なロガーメソッド呼び出しに置き換えることで、コードベースのシンプルさとパフォーマンスの両立が可能になりました。変更範囲は限定的で、既存の log_levels ハッシュや外部インタフェースはそのまま保持されるため、下位互換性も維持されています。
まとめ
この PR は Evented LogSubscriber と StructuredEventSubscriber からプロック定数を除去し、シンプルなデータ構造と直接的なロガーレベル判定へと置き換えることで、Ractor 安全かつ可読性の高い実装に改めました。動作は従来と変わらず、Ractor を活用したマルチスレッド環境でも安全に利用できるようになります。