I'm trying to enable verbose query logging in Rails, but the ActiveRecord::LogSubscriber
is behaving oddly, which is causing the verbose query logging to not work.
The issue seems to be originating here: https://github.com/rails/rails/blob/v6.0.0/activerecord/lib/active_record/log_subscriber.rb#L113
If I inspect
the locations
variable, I can see that it does have content:
[
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/log_subscriber.rb:100:in `debug'",
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/log_subscriber.rb:45:in `sql'",
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/subscriber.rb:145:in `finish'",
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/log_subscriber.rb:107:in `finish'",
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/notifications/fanout.rb:160:in `finish'",
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/notifications/fanout.rb:62:in `block in finish'",
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/notifications/fanout.rb:62:in `each'",
...
]
But once it's gone through the clean
method, it's an empty array. So this:
backtrace_cleaner.clean(locations.lazy).first
is returning nil
. Therefore, the source
assignment on line 105 is nil
, which means the if source
on line 107 evaluates to false, and doesn't log anything.
I've added these two puts
into ActiveRecord::LogSubscriber
:
def log_query_source
source = extract_query_source_location(caller)
puts "-----\n-\n-\nCALLERS: #{caller.inspect}\n-\n-\n-----"
if source
logger.debug(" ↳ #{source}")
end
end
def extract_query_source_location(locations)
puts "-----\n-\n-\nCLEANER: #{backtrace_cleaner.instance_values}\n-\n-\n-----"
backtrace_cleaner.clean(locations.lazy).first
end
to help gather some context about what's actually being registered as a caller and what the backtrace cleaner has established for filtering and silencing.
Here's the output for callers (I'm just including enough to show that there are sources listed from within our application):
[
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/log_subscriber.rb:100:in `debug'",
...
"/Users/jeff/Development/lead-simple/LeadSimple/components/api/app/graph/api/loaders/association_loader.rb:47:in `preload_association'",
"/Users/jeff/Development/lead-simple/LeadSimple/components/api/app/graph/api/loaders/association_loader.rb:34:in `perform'",
...
"/Users/jeff/Development/lead-simple/LeadSimple/components/api/app/controllers/api/graphql_controller.rb:16:in `execute'",
...
"/Users/jeff/Development/lead-simple/LeadSimple/components/api/lib/api/graphql_reloader.rb:18:in `call'",
...
"/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/puma-5.6.5/lib/puma/thread_pool.rb:147:in `block in spawn_thread'"
]
And here's the output for the backtrace cleaner:
{
"silencers"=>[
#<Proc:0x0000000108343330@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/backtrace_cleaner.rb:101>,
#<Proc:0x00000001083432e0@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/backtrace_cleaner.rb:105>,
#<Proc:0x00000001083431c8@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/railties-6.0.0/lib/rails/backtrace_cleaner.rb:19>
],
"filters"=>[
#<Proc:0x0000000108343380@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/backtrace_cleaner.rb:97>,
#<Proc:0x0000000108343240@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/railties-6.0.0/lib/rails/backtrace_cleaner.rb:16>,
#<Proc:0x0000000108343218@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/railties-6.0.0/lib/rails/backtrace_cleaner.rb:17>,
#<Proc:0x00000001083431f0@/Users/jeff/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/railties-6.0.0/lib/rails/backtrace_cleaner.rb:18>
],
"root"=>"/Users/jeff/Development/lead-simple/LeadSimple/"
}
As you can see, nothing specific to our application code is being silenced (or even filtered). However, there is that "root"
instance value, which is very odd because if you look at the source code for ActiveRecord::BacktraceCleaner
, the only instance variables that are defined are @filters
and @silencers
.
I'm guessing that that's involved here, since the path set as the value of "root"
would be removing our application-level traces (if being used as a silencer). The problem is that I have no idea where that's coming from.
The only thing I could guess is that one of our gems is overloading ActiveSupport::BacktraceCleaner
to add the @root
instance variable and some additional layer of silencing based on that. ¯_(ツ)_/¯
Are you testing verbose logging by looking at what the Rails server is logging or by looking at activity in the Rails console? This line will always turn off verbose query logging for activity performed in the console:
# ActiveRecord::Railtie
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
require "active_record/base"
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
ActiveRecord::Base.verbose_query_logs = false # <--- HERE
end
Here's the commit that added that line. The commit message provides more details and refers back to this issue. This is intentional and was done to reduce noise in the Rails console.
If you want to turn verbose logging back on in the Rails console, you would have to manually reset the flag with:
ActiveRecord::Base.verbose_query_logs = true
Or you could add this in an initializer in your app to turn it on whenever the console boots:
# config/initializers/verbose_logging.rb
Rails.application.console do
ActiveRecord::Base.verbose_query_logs = true
end
Note that the Rails Guide suggests not turning this on in Production because of the memory usage, so you may want to keep it off in that environment depending upon the resources available where you run your Production console.
When the verbose logging is made, Rails will attempt to drop out standard Ruby code references and gem reference so that the source location within the app is displayed. This is done using the cleaner in ActiveRecord::LogSubscriber.backtrace_cleaner
which by default is the Rails::BacktraceCleaner
which inherits from the ActiveSupport::BacktraceCleaner
.
To test if the Rails::BacktraceCleaner
is accidentally cleaning out your code you could do something like this:
# Get the source location for a class method somewhere in your app
path = MyClass.method(:my_class_method).source_location.first
# Run it through the BacktraceCleaner
backtrace_cleaner = Rails::BacktraceCleaner.new
backtrace_cleaner.clean([path])
If that returns []
you might want to check to see if you put your app code in the RbConfig::CONFIG["rubylibdir"]
folder. The BacktraceCleaner will assume that code there is a part of the standard Ruby library and will remove it from the backtrace.
The Rails::BacktraceCleaner
also excludes code that isn't found in the app
, config
, lib
, or test
subdirectories. That's done here.
Your root
says it is:
/Users/jeff/Development/lead-simple/LeadSimple/
And then the subfolder beneath that is:
/Users/jeff/Development/lead-simple/LeadSimple/components/...
Rails will clean that out of the backtrace. You can check with something like:
test = ['/Users/jeff/Development/lead-simple/LeadSimple/components/api/app/graph/api/loaders/association_loader.rb']
backtrace_cleaner = Rails::BacktraceCleaner.new
# Filtering will drop off everything before the root
filtered = backtrace_cleaner.send(:filter_backtrace, [path])
=> ["components/api/app/graph/api/loaders/association_loader.rb"]
# Silencing will drop off paths that don't start with app, config, lib, or test
backtrace_cleaner.send(:silence, filtered)
=> []
To avoid this you could:
Rails::BacktraceCleaner
, orRails::BacktraceCleaner::APP_DIRS_PATTERN
in an initializerThe last option would be the easiest but will throw already initialized constant
errors when the app boots.