ruby
43 lines · 7 steps
Logging slow SQL queries in Rails
A module that subscribes to ActiveRecord's SQL notifications and warns whenever a query crosses a duration threshold.
Explained by
highlit
1module SlowQueryLogger
2 SLOW_QUERY_THRESHOLD_MS = 200.0
3 IGNORED_PAYLOAD_NAMES = %w[SCHEMA TRANSACTION].freeze
4
5 module_function
6
7 def subscribe!
8 ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
9 event = ActiveSupport::Notifications::Event.new(*args)
10 log_if_slow(event)
11 end
12 end
13
14 def log_if_slow(event)
15 return if event.duration < SLOW_QUERY_THRESHOLD_MS
16 return if IGNORED_PAYLOAD_NAMES.include?(event.payload[:name])
17 return if event.payload[:cached]
18
19 Rails.logger.warn do
20 binds = format_binds(event.payload[:type_casted_binds])
21 <<~LOG.squish
22 [SlowQuery] #{event.duration.round(1)}ms
23 name=#{event.payload[:name].inspect}
24 sql=#{event.payload[:sql].squish.truncate(500)}
25 binds=#{binds}
26 source=#{query_source}
27 LOG
28 end
29 end
30
31 def format_binds(binds)
32 return "[]" if binds.blank?
33
34 casted = binds.respond_to?(:call) ? binds.call : binds
35 casted.map { |value| value.inspect.truncate(50) }.join(", ")
36 end
37
38 def query_source
39 Rails.backtrace_cleaner.clean(caller).find do |line|
40 line.include?("/app/")
41 end || "unknown"
42 end
43end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1ActiveSupport::Notifications lets you observe SQL without patching ActiveRecord internals.
- 2Guard clauses filter out noise like cached hits and schema queries before you pay logging costs.
- 3Passing a block to Rails.logger.warn defers expensive string building until the level is actually enabled.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
ruby
class ApplicationController < ActionController::Base EXPERIMENTS = { checkout_button_color: %w[control blue green], onboarding_flow: %w[control streamlined]
How A/B test cohorts are assigned in Rails
a-b-testing
cookies
hashing
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/logging-slow-sql-queries-in-rails-explained-ruby-ca25/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.