Unlocking Rails Internals with ActiveSupport Instrumentation


Instrumentation is a collective term for measuring instruments, used for indicating, measuring, and recording physical quantities. It is also a field of study about the art and science about making measurement instruments, involving the related areas of metrology, automation, and control theory. The term has its origins in the art and science of scientific instrument-making.
The Rails ActiveSupport Instrumentation API allows you to create hooks into any part of your app. What does that actually mean? Let’s boil it down.
A practical example
Imagine your app has a document download feature, and you want to track:
- How long each download takes
- Which users are downloading what
- If there are any failed downloads
- The size of downloaded files
Here’s how you might implement this using ActiveSupport::Notifications:
# In your controller or service object
def download_document(document)
ActiveSupport::Notifications.instrument(
"document.download",
document_id: document.id,
user_id: current_user.id,
file_size: document.byte_size
) do
# Your actual download logic here
document.download
end
end
Then you can subscribe to these events anywhere in your application:
# config/initializers/instrumentations.rb
ActiveSupport::Notifications.subscribe("document.download")
do |name, started, finished, unique_id, payload|
DownloadMetric.create!(
document_id: payload.document_id,
user_id: payload.user_id,
file_size: payload.byte_size,
name:,
started:,
finished:,
unique_id:,
)
end
Each time a document is downloaded, your app will create a corresponding DownloadMetric record. This data can be useful for various reasons—from performance monitoring to usage analytics—helping you prioritize future improvements.
Built-in Rails Instrumentations
Rails instruments many of its internal components. Here are some common ones you might want to subscribe to:
# config/initializers/instrumentations.rb
# ...
# Track SQL queries
ActiveSupport::Notifications.subscribe "sql.active_record" do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
puts event.name
puts event.payload[:sql]
puts event.duration
end
Add that to your config/initializers/instrumentations.rb
file go to your Rails console and run:
User.first
You should see the sql.active_record
event being triggered, the the SQL query and the duration it took to execute.
Now, let's say you want to track the duration of each action in your Rails app. You can do that by subscribing to the process_action.action_controller
event:
# Track Action Controller actions
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event|
puts event.name
puts event.payload[:controller]
puts event.payload[:action]
puts event.payload[:status]
puts event.duration
# Do something with the data
if event.duration > 400
# TODO: log the slow action
end
end
Run your Rails app refresh any page and you should see the process_action.action_controller
event being triggered in the logs.
Best Practices
When instrumenting your code:
- Use namespaced event names (e.g., document.download, user.login).
- Include relevant IDs and metadata in the payload.
- Keep the instrumented block as specific as possible.
- Consider performance impact of your subscribers.
- Use async subscribers for heavy processing.
Real-world Use Cases
- Performance monitoring
- User behavior analytics
- Debug logging
- Audit trails
- Resource usage tracking
- Application metrics
- Business intelligence gathering
In this post, we explored the basics of ActiveSupport Instrumentation, covering how to instrument your code and subscribe to events. With these tools, you can gain valuable insights into your application’s behavior and performance.