pockestrap

Programmer's memo

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_cleanerActiveSupport::BacktraceCleanerインスタンスです。 https://github.com/rails/rails/blob/v6.0.3.2/activerecord/lib/active_record/log_subscriber.rb#L7

ActiveSupport::BacktraceCleaneradd_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_cleaneradd_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に置きました。