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に置きました。