kakudooo docs

Railsのログ機構について調べてみた

Railsのログに関連する機能を整理しておく。

基本的な使い方

config/application.rbまたは、config/environments/配下の環境毎の設定ファイルで設定する。

config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")

または、config/initializers配下のファイルで

Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")

初期化後もアプリケーション実行中にRails.loggerや設定を変更することが可能

Rails.logger.level = 0

参考: Rails Loggerについて

全体観

map of logger

Rails.logger

Railsからログ出力するためのloggerインスタンスを保持するグローバルなクラス変数。 基本的には、後述するActiveSupport::Loggerクラス(もしくはその継承クラス)のインスタンスが設定される。

railties/lib/rails.rbに定義されている。

module Rails
  ...
  class << self
    ...
    attr_accessor :app_class, :cache, :logger
    ...
  end
  ...
end

railties/lib/rails/application/bootstrap.rbRails.loggerが初期化される。

  1. config/application.rbconfig/environments/配下の設定ファイル
  2. 設定がなければ、ブロック内でデフォルトのloggerインスタンスを初期化
    1. デフォルトでは、ActiveSupport::TaggedLoggingのインスタンスが指定される

するようになっている

module Rails
  module Bootstrap
    ...
    initializer :initialize_logger, group: :all do
      Rails.logger ||= config.logger || begin
        ...
      end
      ...
    end
    ...
  end
end

ActiveSupport::Loggerクラス

Ruby標準のLoggerクラスを継承したクラス。 Ruby標準のLoggerクラスの薄いWrapperという理解が正しそう。スレッドセーフにログレベルを設定することができる拡張などがされている。

activesupport/lib/active_support/logger.rb

カスタムロガーを作成する際の考慮ポイント

Rails.loggerに指定するloggerはRuby標準のLoggerクラスのインターフェースに準拠することで、独自のものを作成することも可能。 また、互換性を保つために、以下のようなルールで作成する必要がある。

class MyLogger < ::Logger
  include ActiveSupport::LoggerSilence
end

mylogger           = MyLogger.new(STDOUT)
mylogger.formatter = config.log_formatter
config.logger      = ActiveSupport::TaggedLogging.new(mylogger)

参考: https://railsguides.jp/configuring.html#config-logger

ActiveSupport::BroadcastLoggerクラス

複数のIOに向けてログを出力するために、別々のLoggerインスタンスを統合する機能を提供するクラス.

stdout_logger = Logger.new(STDOUT)
file_logger   = Logger.new("development.log")
broadcast = BroadcastLogger.new(stdout_logger, file_logger)

broadcast.info("Hello world!") # Writes the log to STDOUT and the development.log file.

扱いとしては、ActiveSupport::Loggerクラスと同じで、標準のLoggerクラスと同じメソッドを使うことができる。 実際のところは、登録されたLoggerインスタンスに処理を移譲しているだけ。

activesupport/lib/active_support/broadcast_logger.rb

参考: https://api.rubyonrails.org/classes/ActiveSupport/BroadcastLogger.html

ActiveSupport::TaggedLoggingクラス

Ruby標準のLoggerクラスを継承したクラスのオブジェクトをWrapし、ログに対してのタグ付け機能を追加するためのクラス。

ブロックを使う方法

logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged('BCX') { logger.info 'Stuff' }                                  # Logs "[BCX] Stuff"
logger.tagged('BCX', "Jason") { |tagged_logger| tagged_logger.info 'Stuff' }  # Logs "[BCX] [Jason] Stuff"
logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } }       # Logs "[BCX] [Jason] Stuff"

ブロックなし

logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX").info "Stuff"                 # Logs "[BCX] Stuff"
logger.tagged("BCX", "Jason").info "Stuff"        # Logs "[BCX] [Jason] Stuff"
logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"

参考: TaggedLogging

activesupport/lib/active_support/tagged_logging.rbでloggerが拡張される。

それぞれ拡張している。

module ActiveSupport
  module TaggedLogging
    module Formatter
      ...
    end
    ...
    def self.new(logger)
      ...
      logger.formatter.extend Formatter
      logger.extend(self)
    end
    ...
  end
end

ActiveSupport::LogSubscriberクラス

ActiveSupport::Notificationイベントをログとして出力するためのSubscriberクラス。 loggerそのものではなく、フレームワークやアプリケーション上のイベントを購読してログ出力をするなど、ログ出力にあたっての後工程のカスタマイズを容易にする。

attach_toactive_recordnamespaceをActiveSupport::Notifications(Publisher)に登録することで、該当のイベントを購読することができる。

module ActiveRecord
  class LogSubscriber < ActiveSupport::LogSubscriber
    attach_to :active_record

    def sql(event)
      info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
    end
  end
end

ActiveSupport::Notificationsの仕組みについては、また別で取り上げることにする。