Rails.loggerを拡張して
Rails.logger.info("メッセージ", { hoge: "fuga" })
のようなインターフェースで、JSON形式で構造化されたテキストの出力を目指す回
Logger
クラスを継承したStructuredLogger
クラスを作成する。
config/structured_logger.rb
class StructuredLogger < ::Logger
include ActiveSupport::LoggerSilence
class JsonFormatter < ::Logger::Formatter
def call(severity, timestamp, progname, msg)
json = {
severity:,
timestamp:,
progname:,
msg:
}.to_json
json
end
end
def info(message = nil, data = {}, &block)
super({
message:,
data:
}, &block)
end
def error(message = nil, data = {}, &block)
super({
message:,
data:
}, &block)
end
end
config/development.rb
logger = StructuredLogger.new("development.log")
logger.formatter = StructuredLogger::JsonFormatter.new
config.logger = logger
手始めにinfo
,error
メソッドをoverloadして引数にHashを渡せるようにしてみる。
Rails.logger.info("メッセージ", { hoge: "fuga" })
このコードで、Rails.logger.info
を呼び出すと、BroadcastLoggerとの兼ね合いでエラーになってしまう。
{"severity":"ERROR","timestamp":"2025-09-20T07:25:57.110958Z","progname":null,"message":"Could not log \"start_processing.action_controller\" event. ArgumentError: wrong number of arguments (given 2, expected 0..1) ...
なぜかと言うと、Rails.loggerは初期化時にBroadcastLoggerでラップされるようになっているから。
...
if Rails.logger.is_a?(ActiveSupport::BroadcastLogger)
if config.broadcast_log_level
Rails.logger.level = ActiveSupport::Logger.const_get(config.broadcast_log_level.to_s.upcase)
end
else
Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
Rails.logger = ActiveSupport::BroadcastLogger.new(Rails.logger)
end
...
rails_semantic_loggerもこの挙動を抑制するためにモンキーパッチにより、BroadcastLoggerの機能を抑制している。
なるべく既存のコードを変更したくはないので、Rails.logger
のインターフェースは維持しつつ、引数の型に応じたログの出力を行うことにした。
info
,error
などのメソッドのインターフェースはそのままで、引数としてHashを受取りFormatterで引数に応じた処理を行うようにする。
config/structured_logger.rb
class StructuredLogger < ::Logger
include ActiveSupport::LoggerSilence
class JsonFormatter < ::Logger::Formatter
def call(severity, timestamp, progname, msg)
base = {
severity,
timestamp,
progname
}
payload = case msg
when Hash then
msg
else
{ message: msg }
end
base.merge(payload).to_json
end
end
end
出力結果
{"severity":"INFO","timestamp":"2025-09-20T07:37:19.349181Z","progname":null,"hoge":"fuga"}
ActiveSupport::LogSubscriberを拡張し、ActionControllerのログをJSON形式で出力できるようにしていく。