Unlocking Rails Internals with ActiveSupport Instrumentation

February 08, 2025

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:

  1. Use namespaced event names (e.g., document.download, user.login).
  2. Include relevant IDs and metadata in the payload.
  3. Keep the instrumented block as specific as possible.
  4. Consider performance impact of your subscribers.
  5. 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.


References

Docs