ActiveSupport::ProxyLogger の silence がブロック単位でログ抑制を正しく行うように修正
ActiveSupport::ProxyLogger が silence を呼び出してもログが抑制されなかった問題を解消し、ブロック内での一時的なログレベル制御が期待通りに動作するようになりました。
背景
silence が無効化されていたため、ProxyLogger 経由でのログ抑制が効かず、Rails 内部でのマイグレーション時などの一時的なサイレンス処理が破綻していました。ProxyLogger は LoggerSilence を include していますが、現在の実装では silencer フラグが常に true に設定されており、実際のレベル制御が行われませんでした。結果として、proxy.silence { proxy.info("noise") } が期待通りに何も出力しないという基本的な契約が守られていませんでした。これにより、Rails が内部で依存している logger.silence の振る舞いと不整合が生じていました。
この不整合は、将来的に ProxyLogger が正式リリースされる際に既存コードとの互換性問題を引き起こす可能性があり、早期に修正する必要がありました。特に、マイグレーションやタスク実行時に大量のノイズログが出力されることは、デバッグや運用上の負荷を増大させます。したがって、silence の正しい動作を保証する実装変更が求められました。
結論として、ProxyLogger が LoggerSilence のロジックを正しく利用できるようにすることが、本変更の根本的な目的です。
技術的な変更
ProxyLogger のインスタンスレベルで一時的なログレベルを保持できるようにしたことで、silence がブロック内だけ有効になるように実装が変更されました。initialize メソッドに super() を追加し、level アクセサをメソッド化して local_level || @level を返すようにしました。これにより、LoggerSilence が内部で利用する log_at が local_level を参照し、ブロック実行中だけレベルが下げられます。
変更前:
attr_reader :level
変更後:
def level
local_level || @level
end
さらに、initialize に super() を呼び出すことで、LoggerSilence が期待する内部初期化が行われ、silence が正しく機能する基盤が整いました。テスト追加により、silence がロガー自身の出力だけを抑制し、同一基底ロガーを共有する他の ProxyLogger には影響しないことが確認されています。
結果として、ProxyLogger は ActiveSupport::Logger と同様の silence 挙動を示し、既存コードへの影響は最小限に抑えられました。ブロック外の通常ログは従来通り出力され、ブロック内の低レベルログだけが抑制されます。
設計判断
既存の LoggerSilence モジュールを再利用しつつ、ProxyLogger にのみ局所的なレベル管理を導入する方針が採られました。新たなモジュールやフラグを追加する代わりに、local_level というインスタンス変数を活用し、silence の範囲をインスタンス単位に限定することで後方互換性を維持しました。
この設計は、複数の ProxyLogger が同一の基底ロガーを共有しても、サイレンス効果が交叉しないことを保証します。silence がブロック実行中だけ local_level を上書きし、ブロック終了時に元のレベルに復元されるため、他プロキシへの副作用が排除されます。変更点は ProxyLogger の内部ロジックに留まり、外部インターフェースは一切変わりません。
結論として、最小限のコード追加で既存設計と整合性を保ちつつ、期待通りのログ抑制機能を提供できる実装となっています。
まとめ
ActiveSupport::ProxyLogger が silence メソッドで一時的なログレベル抑制を正しく行えるようになり、Rails が内部で依存するサイレンス機構との整合性が取れました。インスタンス単位の local_level 導入により、同一基底ロガーを共有する他プロキシへの影響を防ぎつつ、既存 API の互換性を維持しています。