動作確認したRailsのバージョン
6.0.3.4
よくある話として
NginxなどのWebサーバのログとunicornやpumaなどのアプリサーバのログを同じIDで検索するため、X-Request-IDをアプリサーバのログに仕込んだりします。Railsに予め用意されているconfigの設定で行うのが手軽ですが、下記のような問題があります。
TaggedLoggingの問題
TaggedLoggingを利用していれば、configファイルの以下の設定のコメントアウトを外せばX-Request-IDがログに付与されるようになっています。
config.log_tags = [ :request_id ]
X-Request-IDが123456789だとした時、↓のようなログになります。
[2020-11-XXT09:37:34+09:00] [123456789] some message
しかし、TaggedLoggingは例えば下記のような処理がある時、backtraceをログに出力してくれません。
def XXX
rescue StandardError => e
logger.error e
end
そんな時はRails 5.2から実装されたCurrentAttributesを使うことで同じことを実現できます。
CurrentAttributesの
ドキュメントにはrequest_idという単語があって、こういうユースケースを想定していることが想像できますね。
ドキュメントにあったコード例
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :account, :user
attribute :request_id, :user_agent, :ip_address
resets { Time.zone = nil }
def user=(user)
super
self.account = user.account
Time.zone = user.time_zone
end
end
実装方法
流れとしてはmiddlewareでX-Request-IDをCurrentAttributesに設定して、formatterでそれを出力するだけです。
ActiveSupport::CurrentAttributesを継承したクラスを作成
app/models/current.rb
# frozen_string_literal: true
class Current < ActiveSupport::CurrentAttributes
attribute :x_request_id
end
middlewareを使って作成したクラスに値をセットします、
config/initializers/x_request_id.rb
require 'current_x_request_id'
Rails.application.config.middleware.insert_after ActionDispatch::RequestId, CurrentXRequestId
ActionDispatch::RequestIdでX-Request-IDがenv['action_dispatch.request_id']にセットされます
lib/current_x_request_id.rb
# frozen_string_literal: true
class CurrentXRequestId
def initialize(app)
@app = app
end
def call(env)
Current.x_request_id = env['action_dispatch.request_id']
@app.call(env)
end
end
あとはformatterでそれを出力する
lib/formatter.rb
def call(severity, time, progname, msg)
msg = msg2str(msg) # backtraceを出力するのに親クラス(Logger::Formatter)のメソッド(msg2str)を利用
~~~
"request_id: #{Current.x_request_id} #{msg}"
~~~
end
注意点として
CurrentAttributesはとても強力な仕組みですが、使う際はよく検討する必要がありそうです。注意書きの一例を載せておきます。
A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
Current should only be used for a few, top-level globals, like account, user, and request details.
The attributes stuck in Current should be used by more or less all actions on all requests. If you start
sticking controller-specific attributes in there, you're going to create a mess.
deepl翻訳
Currentのようなグローバルなシングルトンをやりすぎて、結果的にモデルが絡まってしまうことがあります。
Currentは、アカウント、ユーザー、リクエストの詳細などの少数のトップレベルのグローバルにのみ使用されるべきです。
Current に固定された属性は、多かれ少なかれすべてのリクエストのすべてのアクションで使用されるべきです。もし
コントローラー固有の属性を貼り付けてしまうと、混乱を招くことになります。