Arproxy is a library that can intercept SQL queries executed by ActiveRecord to log them or modify the queries themselves.
Create your custom proxy and add its configuration in your Rails' config/initializers/
directory:
class QueryTracer < Arproxy::Proxy
def execute(sql, context)
Rails.logger.debug sql
Rails.logger.debug caller(1).join("\n")
super(sql, context)
end
end
Arproxy.configure do |config|
config.adapter = 'mysql2' # A DB Adapter name which is used in your database.yml
config.use QueryTracer
end
Arproxy.enable!
Then you can see the backtrace of SQLs in the Rails' log.
# In your Rails code
MyTable.where(id: id).limit(1) # => The SQL and the backtrace appear in the log
context
is an instance of Arproxy::QueryContext
and contains values that are passed from Arproxy to the Database Adapter.
context
is a set of values used when calling Database Adapter methods, and you don't need to use the context
values directly.
However, you must always pass context
to super
like super(sql, context)
.
For example, let's look at the Mysql2Adapter implementation. When executing a query in Mysql2Adapter, the Mysql2Adapter#internal_exec_query
method is called internally.
# https://github.com/rails/rails/blob/v7.1.0/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb#L21
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
# ...
end
In Arproxy, this method is called at the end of the Arproxy::Proxy#execute
method chain, and at this time context
contains the arguments to be passed to #internal_exec_query
:
member | example value |
---|---|
context.name |
"SQL" |
context.binds |
[] |
context.kwargs |
{ prepare: false, async: false } |
You can modify the values of context
in the proxy, but do so after understanding the implementation of the Database Adapter.
In the Rails' log you may see queries like this:
User Load (22.6ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Issei Naruta'
Then "User Load"
is the context.name
.
Without Arproxy:
+-------------------------+ +------------------+
| ActiveRecord::Base#find |--SQL-->| Database Adapter |
+-------------------------+ +------------------+
With Arproxy:
Arproxy.configure do |config|
config.adapter = 'mysql2'
config.use MyProxy1
config.use MyProxy2
end
+-------------------------+ +----------+ +----------+ +------------------+
| ActiveRecord::Base#find |--SQL-->| MyProxy1 |-->| MyProxy2 |-->| Database Adapter |
+-------------------------+ +----------+ +----------+ +------------------+
Arproxy supports the following databases and adapters:
- MySQL
mysql2
,trilogy
- PostgreSQL
pg
- SQLite
sqlite3
- SQLServer
activerecord-sqlserver-adapter
We have tested with the following versions of Ruby, ActiveRecord, and databases:
- Ruby
3.0
,3.1
,3.2
,3.3
- ActiveRecord
6.1
,7.0
,7.1
,7.2
,8.0
- MySQL
9.0
- PostgreSQL
17
- SQLite
3.x
(not specified)
- SQLServer
2022
class CommentAdder < Arproxy::Proxy
def execute(sql, context)
sql += ' /*this_is_comment*/'
super(sql, context)
end
end
class SlowQueryLogger < Arproxy::Proxy
def initialize(slow_ms)
@slow_ms = slow_ms
end
def execute(sql, context)
result = nil
ms = Benchmark.ms { result = super(sql, context) }
if ms >= @slow_ms
Rails.logger.info "Slow(#{ms.to_i}ms): #{sql}"
end
result
end
end
Arproxy.configure do |config|
config.use SlowQueryLogger, 1000
end
If you don't call super
in the proxy, you can block the query execution.
class Readonly < Arproxy::Proxy
def execute(sql, context)
if sql =~ /^(SELECT|SET|SHOW|DESCRIBE)\b/
super(sql, context)
else
Rails.logger.warn "#{context.name} (BLOCKED) #{sql}"
nil
end
end
end
# any_gem/lib/arproxy/plugin/my_plugin
module Arproxy::Plugin
class MyPlugin < Arproxy::Proxy
Arproxy::Plugin.register(:my_plugin, self)
def execute(sql, context)
# Any processing
# ...
super(sql, context)
end
end
end
Arproxy.configure do |config|
config.plugin :my_plugin
end
See UPGRADING.md
$ git clone https://github.com/cookpad/arproxy.git
$ cd arproxy
$ bundle install
$ bundle exec appraisal install
To run all tests with all supported versions of ActiveRecord:
$ docker compose up -d
$ bundle exec appraisal rspec
To run tests for a specific version of ActiveRecord:
$ bundle exec appraisal ar_7.1 rspec
or
$ BUNDLE_GEMFILE=gemfiles/ar_7.1.gemfile bundle exec rspec
Arproxy is released under the MIT license: