Rails4
bugsnag

ActiveSupport::Logger.broadcast を使って Bugsnag.notify した時の小話

More than 3 years have passed since last update.

サンプルがてら 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 に渡す例外オブジェクトの作り方で制御できそうな気はするけど、力不足でそこまで調べられていない。