How can I squish SQL for log tidiness (automatically)?
Hello everyone,
I’m dealing with a challenge in my codebase where there are numerous multiline SQL heredocs being logged. While these may be helpful for local debugging, they become a hassle when grepping through logs in production. Searching for specific queries often requires running grep with the -C
flag (or restarting grep multiple times with adjusted context), which is inefficient and frustrating for an everyday task like log analysis. For this reason, I want to prioritize tidy, single-line SQL logs that are easier to grep.
While I know that applying .squish
to individual queries can help, it’s not developer-friendly and requires everyone to remember to do this consistently. Additionally, it doesn’t always work—for example, when the query begins with a single-line comment.
To address this, I’ve tried dynamically squishing the SQL using a custom LogSubscriber, as shown below. However, I’m concerned that modifying event.payload[:sql] could have unintended side effects when the event is processed elsewhere:
class SQLSquishLogSubscriber < ActiveRecord::LogSubscriber
def sql(event)
return super if Rails.env.local?
sql = event.payload[:sql]
return super if sql.blank?
event.payload[:sql] = sql.squish
super
end
end
Does anyone have a better solution for ensuring clean, single-line SQL logs without risking side effects or placing extra burden on developers?
Thanks in advance!
Is that a better approach?
class SQLSquishLogSubscriber < ActiveRecord::LogSubscriber
def sql(event)
return super if Rails.env.local?
original_sql = event.payload[:sql]
return super if original_sql.blank?
event.payload[:sql] = original_sql.squish
super
ensure
event.payload[:sql] = original_sql
end
end
A couple things come to mind:
- Unless you have other things parsing the SQL, squishing it should be fine.
- You probably add to the payload instead of modifying it.
event.payload[:squished_sql] = event.payload[:sql].squish
Thanks, but my goal was to not copy the original code from the rails codebase and adjust the line to log the sql (e.g. I didn't want to do something like):
# original code derived from ActiveRecord::LogSubscriber
def sql(event)
...
squished_sql = event.payload[:sql].squish
debug " #{name} #{squished_sql}#{binds}"
end
I think this works out:
class SQLSquishLogSubscriber < ActiveRecord::LogSubscriber
def sql(event)
return super unless squish_sql_in_logs?
event_dup = event.dup.tap do |e|
e.payload = e.payload.dup
e.payload[:sql] = e.payload[:sql].squish if e.payload[:sql]
end
super(event_dup)
end
private
def squish_sql_in_logs?
conf = Rails.application.config.x.logger.squish_sql
conf.nil? ? true : conf
end
end