RailsのActiveSupport::TaggedLogging
モジュールを参考にして、以下のようなタグを構造化ログに設定できるようにする。
{
...
"tags": [
"0123456789"
]
}
大まかな処理の流れとしては、以下
1.
をストレージとして、タグを記録する以前のpartで作成したconfig/structured_logger.rb
をタグ付けに対応させてみる。
ActiveSupport::TaggedLogging
からタグを追加する最低限の機能だけを雑に実装してみると以下のようになる。
※ 本記事のタグ管理は検証用の簡易実装であり、タグのpopやブロック定義の対応など本番運用向けの設計は考慮していない。
class StructuredLogger < ::Logger
include ActiveSupport::LoggerSilence
class TagStack
attr_reader :tags
def initialize
@tags = []
end
def format_message(message)
if @tags.empty?
message
else
{
**message,
tags: @tags
}
end
end
def push_tags(tags)
tags.flatten!
tags.reject!(&:blank?)
@tags.concat(tags)
tags
end
end
class JsonFormatter < ::Logger::Formatter
def call(severity, timestamp, progname, msg)
base = {
severity: severity.to_s,
timestamp: timestamp.utc.iso8601(6),
progname: progname
}
# TODO: データ型によって、紐づけ方を変える必要がある
payload =
case msg
when Hash then tag_stack.format_message(msg) # MEMO: 一旦のところHashの場合のみタグに対応。ログのフォーマット時に合わせて保持しているタグを`tags`属性に設定する
when Exception then { exception: { error_class: msg.class.name, message: msg.message, backtrace: msg.backtrace } }
else { message: msg.to_s }
end
(base.merge(payload)).to_json << "\n"
end
# Thread or Fiber(デフォルトはThread)を使用して、タグをスレッド内で保持する
def tag_stack
# We use our object ID here to avoid conflicting with other instances
@thread_key ||= "structured_tagged_logging_tags:#{object_id}"
ActiveSupport::IsolatedExecutionState[@thread_key] ||= TagStack.new
end
def current_tags
tag_stack.tags
end
def push_tags(*tags)
tag_stack.push_tags(tags)
end
end
# logger.tagged("タグ").info(...)のようなインターフェースでタグを登録できるようにする
def tagged(*tags)
formatter.push_tags(*formatter.current_tags, *tags)
self
end
end
呼び出し側
logger.tagged("sample").info({
message: "hoge!!",
data: {
hoge: "fuga"
}
})
出力結果
{"severity":"INFO","timestamp":"2025-09-28T02:59:20.222436Z","progname":null,"message":"hoge!!","data":{"hoge": "fuga"},"tags":["sample"]}