サンプルがてら gem にしてみたのが以下。
以下、小話。
ActiveSupport::Logger.broadcast
Rails.logger.extend ActiveSupport::Logger.broadcast
を使う事で、複数のロガーに出力できる様になる。
error_logger = ActiveSupport::Logger.new(STDOUT)
error_logger.level = Logger::ERROR
Rails.logger.extend ActiveSupport::Logger.broadcast(error_logger)
上記の様にセットアップすることで、 Rails.logger.error
した時に通常のログと追加したエラーログに対して出力される様になる。
罠
ActiveSupport::Logger.broadcast
は、以下の様にロガーの各メソッドを親に伝播させる働きをするモジュールを作る。そして、このモジュールを Rails.logger.extend
してロガーを拡張していく形になる。
def self.broadcast(logger) # :nodoc:
Module.new do
...
define_method(:level=) do |level|
logger.level = level
super(level)
end
代入系メソッドがやっかいで、自分と親にたいして同じ値を代入する実装になっている。全てのロガーで同じレベルやフォーマッタを使っている場合は問題ないが、そうでない場合に問題になるかもしれない。
ロガーごとにレベルやフォーマッタを切り替えている場合、 Rails.logger.extend
した後に Rails.logger.level =
などとしてしまうと、それまで extend しているロガー全てが同じレベルに設定されてしまう。
quiet_assets
quiet_assets gem を使って、RAILS_ENV development では /assets/ のログを出力しない、という事を良くやる。
quiet_assets は Rails.logger.level= を呼び出すため、broadcast との相性がすこぶる悪いので注意が必要。
ActiveSupport::Logger#silence
LoggerSilence
(ActiveSupport::Logger#silence
) とも相性悪そうに見える。
def silence(temporary_level = Logger::ERROR)
if silencer
begin
old_logger_level, self.level = level, temporary_level
yield self
ensure
self.level = old_logger_level
end
else
yield self
end
end
レベルの異なるロガーを束ねる際は、 ActiveSupport::Logger#broadcast
は使わない方が良い、というかそういう使い方を想定されていない、のかな?
Bugsnag.notify とバックトレース
Bugsnag.notify は Bugsnag にエラー情報を送信できるメソッド。例外オブジェクトのバックトレースや caller を読み取って一緒に送る事もしてくれる。
logger.error
が呼ばれたときに Bugsnag.notify
が呼ばれる様な独自 Logger を実装する場合、バックトレースの先頭が Logger 実装の Bugsnag.notify
呼び出し行になってしまってうれしくない。
ここで、bugsnag-ruby の実装を眺めてみると、バックトレースのファイルパスが lib/bugsnag
と該当する行を読み飛ばしてくれる様になっている。
という事で、そういう階層構造に強引にしてしまうことでそれなりに読みやすいエラー情報にする事ができた。
※ Bugsng.notify に渡す例外オブジェクトの作り方で制御できそうな気はするけど、力不足でそこまで調べられていない。