October 1, 2025
 — Upgrades 

Migrating away from marginalia for query comments under Rails 8

Steve Pike
CEO, Co-founder

Infield upgrades Ruby on Rails apps, and part of that work is adopting new features of Rails to replace something customers were previously relying on a gem for. One such migration is moving from the marginalia gem to Rails’ built-in QueryLogs system.

The marginalia gem annotates SQL statements generated by ActiveRecord with comments about the source of the query. For instance you can add the originating controller, action, or source location of the query. Created in 2013 by the basecamp team, its functionality has since been incorporated into Rails core with the QueryLogs feature and marginalia is no longer compatible as of Rails 8 (we’ve also gotten reports from a few of our customers about missing query log comments under Rails 7.2, even though the gem is technically compatible).

The migration path is to remove the gem and move to the builtin QueryLogs feature. This means porting your marginalia configuration over to the new style. Most options have 1-1 replacements. Issues arise if you’ve patched the Marginalia::Comment class to dynamically generate comment tags at query time or if you’re integrating marginalia with other systems like Sidekiq. Here are a few cases we’ve encountered:

Adding request_id from a request header

To reach into the controller request object while generating a query tag you’ll need a proc:

Rails.application.config.active_record.query_log_tags = [

   request_id: ->(context) { context[:controller]&.request&.env&.dig('REQUEST_ID') },

]

Filtering backtrace lines

A very helpful tag is the line in your source code that the query was generated from. Rails has this built-in with the source_location tag, which replaces the line option from marginalia. However, there is no 1-1 replacement for the old Marginalia::Comment.lines_to_ignore = option. Instead you’ll need to create your own BacktraceSilencer and integrate that:

query_log_backtrace_cleaner = ActiveSupport::BacktraceCleaner.new

query_log_backtrace_cleaner.add_filter { |line| line.delete_prefix("#{Rails.root}/") }

lines_to_ignore_regexp = /tmp/

query_log_backtrace_cleaner.add_silencer do |line|

 lines_to_ignore_regexp.match?(line)

end

Then create a proc that grabs the top of the callstack, applying the cleaner. Note that this only works on Ruby versions 3.2 and above. For earlier rubies you’ll need to use caller_locations (see how Rails does it here).

first_clean_frame = -> do

 Thread.each_caller_location do |location|

   frame = query_log_backtrace_cleaner.clean_frame(location)

   return frame if frame

 end

 nil

end

Then apply your proc as a custom tag in query_log_tags.

Sidekiq integration

It can be helpful to add the name of the sidekiq job that triggered a query as a query comment. You can do this with sidekiq middleware and the ActiveSupport::ExecutionContext, which is available to QueryLogs:

module Sidekiq

 class QueryLogContext

   def call(_worker_class, job, _queue, &)

     sidekiq_job_class = job['class'].to_s

     ActiveSupport::ExecutionContext.set(job: sidekiq_job_class, &)

   end

 end

end

Sidekiq.configure_server do |config|

 config.server_middleware do |chain|

   chain.add Sidekiq::QueryLogContext

 end

end

Rails.application.config.active_record.query_log_tags = [ :job ]


Testing and deployment

SQL query comments are not usually well-covered by automated tests. In fact, some of our customers go so far as to disable query log commenting in tests to speed them up. You’ll need to run queries in a variety of settings manually to compare the comments before/after the migration in order to ensure you’re still capturing the metadata you expect.

We’d recommend doing the migration away from marginalia while you’re still running Rails 7.2 (or even 7.1). That way when you go and do your eventual Rails 8 upgrade you’re not stuck migrating gems at the same time.

We’d love to hear from you if you ran into any issues with this migration or tackled it another way.