Open6

Railsのログに含まれるrequest_idについてコードリーディングしたメモ

bisquebisque

request_idとは、Railsのログに標準で出力されるuuidのことです。リクエスト毎に一意なidが振られるようになっており、ログをトレースして見るのに便利です。

[42acbf94-050b-4728-a828-d6fea488a6ce] Started GET "/" for 172.20.0.1 at 2022-09-27 18:17:54 +0900

また、Request Header からrequest_idの値を設定したり、Response Headerにもrequest_idの値が設定されるので、うまく活用することで他のサービスも含めて串刺しでログをトレースすることができます。

今回、Railsのログフォーマットを構造化されたログに変えようとしたのですが、request_idは引き続き出力されるようにしたいと思い、request_idがどのように設定されるのか、またログフォーマットを変えたときにrequest_idを出力する方法について調査しました。

bisquebisque

request_idが設定される箇所

ActionDispatch::RequestId は rack の middlewareです。middlewareって何っていう話は一旦置いておいて、Request Header に X-Request-Id があればその値を、なければランダムな値を生成してreqオブジェクトのrequest_idに設定していることが分かります。

https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_dispatch/middleware/request_id.rb

reqオブジェクトは引数のenvを拡張したものなので、内部的には env の Response Header に request_id を設定しているのと同じことです。

ActionDispatch::Request の request_id の定義

https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_dispatch/http/request.rb#L310-L312

ActionDispatch::Request は Rack::Requestをincludeしている。Rack::Request の set_header の定義

https://github.com/rack/rack/blob/main/lib/rack/request.rb#L116-L118

bisquebisque

ログにrequest_idが差し込まれる仕組み

Rack::Logger middlewareで設定します。

https://github.com/rails/rails/blob/7-0-stable/railties/lib/rails/rack/logger.rb#L24-L28

if logger.respond_to?(:tagged) は、Rails.loggerがtaggedメソッドを持っていたらという判定です。taggedメソッドは ActiveSupport::TaggedLogging が提供しています。

https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/tagged_logging.rb

つまり、Rails.loggerがActiveSupport::TaggedLoggingであれば logger.tagged(compute_tags(request)) を実行して、後続のmiddlewareへと進みます。

compute_tagsでは @taggers の内容に応じて配列を作成しています。@taggers はデフォルトでは config.log_tags の値が設定されます。この値は、デフォルトで [:request_id] となっています。@taggers の値がsymbolの場合、request.send(tag) が実行されますから、ここでは request.send(:request_id) 、つまり先ほどActionDispatch::Request middlewareで設定されたrequest_idを参照しているわけです。

https://github.com/rails/rails/blob/7-0-stable/railties/lib/rails/rack/logger.rb#L59-L69

bisquebisque

ActiveSupport::TaggedLogging はtagをどうやって引き回しているのか

Rack::Logger middleware で logger.tagged(['リクエストID']) が設定されました。これがアプリケーション内のどの場所でも適用される仕組みが気になります。

taggedメソッドの定義を見てみます。中でyieldしているので、controller以下の処理は全てこのyieldの中で行われていることになります。

https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/tagged_logging.rb#L35-L40

push_tagsメソッドではcurrent_tagsにtagsを追加しています。

https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/tagged_logging.rb#L42-L47

current_tagsは IsolatedExecutionState です。

https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/tagged_logging.rb#L57-L61

中身は Thread.current.active_support_execution_state です。つまり、スレッドセーフなスレッド変数にtagsを格納しているのですね。これにより、アプリケーション内のどの場所でもtagsを参照することができるようになっています。

https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/isolated_execution_state.rb#L61-L63

bisquebisque

ActiveSupport::TaggedLogging を使わずにrequest_idをログに差し込むには?

  1. ActiveSupport::TaggedLoggingと同様に、スレッド変数にrequest_idを格納するrack middlewareを作る
  2. スレッド変数のrequest_idを参照するログFormatterを作る

この2点でできるはずです。(未実装)