Active Recordのクエリの実行元の表示から、特定のパスを除外する
TL;DR
ActiveRecord::LogSubscriber.backtrace_cleaner = Rails::BacktraceCleaner.new.tap do |c| c.add_silencer { |line| line.include?('path/to/meaningless/file.rb') } end
Problem
Active Recordにはクエリの実行元をログに表示する機能があります。
例: users_controller.rb
でクエリが実行されていることが分かる
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 ↳ app/controllers/users_controller.rb:70:in `index'
ところがこれが意図しない位置に表示されることがあります。 Middlewareを自分で定義して使っている場合にこの問題は起きやすいでしょう。
例えば私の例ではGraphQL-Ruby用のmiddlewareを使っていてこの問題に遭遇しました。 https://pocke.hatenablog.com/entry/2020/05/28/113152
Solution
これを回避するには意図しないパスを除外するようにルールを書きます。
まずはクエリの実行位置を表示する仕組みのコードを見てみましょう。これはActiveRecord::LogSubscriber#log_query_source
として定義されています。
https://github.com/rails/rails/blob/v6.0.3.2/activerecord/lib/active_record/log_subscriber.rb#L104-L110
# log_subscriber.rb def log_query_source source = extract_query_source_location(caller) if source logger.debug(" ↳ #{source}") end end
そしてextract_query_source_location
は次のように定義されています。
https://github.com/rails/rails/blob/v6.0.3.2/activerecord/lib/active_record/log_subscriber.rb#L112-L114
# log_subscriber.rb def extract_query_source_location(locations) backtrace_cleaner.clean(locations.lazy).first end
そしてこのbacktrace_cleaner
はActiveSupport::BacktraceCleaner
のインスタンスです。
https://github.com/rails/rails/blob/v6.0.3.2/activerecord/lib/active_record/log_subscriber.rb#L7
ActiveSupport::BacktraceCleaner
はadd_silencer
メソッドを持っており、これを使うと特定のパスを無視できます。
https://github.com/rails/rails/blob/v6.0.3.2/activesupport/lib/active_support/backtrace_cleaner.rb#L64-L71
つまり、次のように書くとクエリの実行元からpath/to/meaningless/file.rb
を除外できます。
ActiveRecord::LogSubscriber.backtrace_cleaner.add_silencer { |line| line.include?('path/to/meaningless/file.rb') }
しかし、これはおそらく望んでいるものではないでしょう。なぜならばbacktrace_cleaner
は、Railsの場合にはRails.backtrace_cleaner
と同じインスタンスがセットされるためです。
https://github.com/rails/rails/blob/v6.0.3.2/activerecord/lib/active_record/railtie.rb#L81
http://github.com/rails/rails/blob/v6.0.3.2/railties/lib/rails/backtrace_cleaner.rb
これはActiveRecord::LogSubscriber.backtrace_cleaner
のadd_silencer
を呼ぶと、Rails.backtrace_cleaner
も変更されることを意味します。
つまり、この例ではpath/to/meaningless/file.rb
はクエリの実行元から消えるのみならず、例外が起きた時のバックトレースからも消えてしまいます。
もし例外のバックトレースからはpath/to/meaningless/file.rb
を消したくない場合、ActiveRecord::LogSubscriber.backtrace_cleaner
に新しくRails::BacktraceCleaner
のインスタンスを作ってセットしてやると良いでしょう。
つまり、最終的には冒頭に上げたのと同じ次のコードが出来上がります。
ActiveRecord::LogSubscriber.backtrace_cleaner = Rails::BacktraceCleaner.new.tap do |c| c.add_silencer { |line| line.include?('path/to/meaningless/file.rb') } end
これを適当にconfig/initializers/
下に配置すると良いでしょう。私はconfig/initializers/backtrace_silencers.rb
に置きました。