Active Support is a part of core Rails that provides Ruby language extensions, utilities, and other things. One of the things it includes is an instrumentation API that can be used inside an application to measure certain actions that occur within Ruby code, such as those inside a Rails application or the framework itself. It is not limited to Rails, however. It can be used independently in other Ruby scripts if desired.
In this guide, you will learn how to use the Active Support's instrumentation API to measure events inside of Rails and other Ruby code.
After reading this guide, you will know:
What instrumentation can provide.
How to add a subscriber to a hook.
The hooks inside the Rails framework for instrumentation.
How to build a custom instrumentation implementation.
The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be subscribed to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
You are even able to create your own events inside your application which you can later subscribe to.
Use ActiveSupport::Notifications.subscribe with a block to listen to any notification. Depending on the amount of arguments the block takes, you will receive different data.
The first way to subscribe to an event is to use a block with a single argument. The argument will be an instance of ActiveSupport::Notifications::Event.
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event|
event.name # => "process_action.action_controller"
event.duration # => 10 (in milliseconds)
event.allocations # => 1826
event.payload # => {:extra=>information}
Rails.logger.info "#{event} Received!"
end
If you don't need all the data recorded by an Event object, you can also specify a block that takes the following five arguments:
Name of the event
Time when it started
Time when it finished
A unique ID for the instrumenter that fired the event
The payload for the event
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
# your own custom stuff
Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received! (started: 2019-05-05 13:43:57 -0800, finished: 2019-05-05 13:43:58 -0800)
end
If you are concerned about the accuracy of started and finished to compute a precise elapsed time, then use ActiveSupport::Notifications.monotonic_subscribe. The given block would receive the same arguments as above, but the started and finished will have values with an accurate monotonic time instead of wall-clock time.
ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
# your own custom stuff
duration = finished - started # 1560979.429234 - 1560978.425334
Rails.logger.info "#{name} Received! (duration: #{duration})" # process_action.action_controller Received! (duration: 1.0039)
end
You may also subscribe to events matching a regular expression. This enables you to subscribe to multiple events at once. Here's how to subscribe to everything from ActionController:
ActiveSupport::Notifications.subscribe(/action_controller/) do |event|
# inspect all ActionController events
end
This event is emitted when a transaction has been started.
Key
Value
:transaction
Transaction object
:connection
Connection object
Please, note that Active Record does not create the actual database transaction until needed:
ActiveRecord::Base.transaction do
# We are inside the block, but no event has been triggered yet.
# The following line makes Active Record start the transaction.
User.count # Event fired here.
end
Remember that ordinary nested calls do not create new transactions:
ActiveRecord::Base.transaction do |t1|
User.count # Fires an event for t1.
ActiveRecord::Base.transaction do |t2|
# The next line fires no event for t2, because the only
# real database transaction in this example is t1.
User.first.touch
end
end
However, if requires_new: true is passed, you get an event for the nested transaction too. This might be a savepoint under the hood:
ActiveRecord::Base.transaction do |t1|
User.count # Fires an event for t1.
ActiveRecord::Base.transaction(requires_new: true) do |t2|
User.first.touch # Fires an event for t2.
end
end
This event is emitted when a database transaction finishes. The state of the transaction can be found in the :outcome key.
Key
Value
:transaction
Transaction object
:outcome
:commit, :rollback, :restart, or :incomplete
:connection
Connection object
In practice, you cannot do much with the transaction object, but it may still be helpful for tracing database activity. For example, by tracking transaction.uuid.
Adding your own events is easy as well. Active Support will take care of all the heavy lifting for you. Simply call ActiveSupport::Notifications.instrument with a name, payload, and a block. The notification will be sent after the block returns. Active Support will generate the start and end times, and add the instrumenter's unique ID. All data passed into the instrument call will make it into the payload.
Here's an example:
ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
# do your custom stuff here
end
Now you can listen to this event with:
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # {:this=>:data}
end
You may also call instrument without passing a block. This lets you leverage the instrumentation infrastructure for other messaging uses.
ActiveSupport::Notifications.instrument "my.custom.event", this: :data
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # {:this=>:data}
end
You should follow Rails conventions when defining your own events. The format is: event.library. If your application is sending Tweets, you should create an event named tweet.twitter.
Feedback
You're encouraged to help improve the quality of this guide.
Please contribute if you see any typos or factual errors. To get started, you can read our documentation contributions section.
You may also find incomplete content or stuff that is not up to date. Please do add any missing documentation for main. Make sure to check Edge Guides first to verify if the issues are already fixed or not on the main branch. Check the Ruby on Rails Guides Guidelines for style and conventions.
If for whatever reason you spot something to fix but cannot patch it yourself, please open an issue.
And last but not least, any kind of discussion regarding Ruby on Rails documentation is very welcome on the official Ruby on Rails Forum.