The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There areseveral 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 isa hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could besubscribed to, and used to track the number of queries during a certain action. There'sanother 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 tocreate your own events inside your application which you can later subscribe to.
UseActiveSupport::Notifications.subscribe with a block to listen to any notification. Depending on the amount ofarguments 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 ofActiveSupport::Notifications::Event.
If you don't need all the data recorded by an Event object, you can also specify ablock 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 stuffRails.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 ofstarted andfinished to compute a precise elapsed time, then useActiveSupport::Notifications.monotonic_subscribe. The given block would receive the same arguments as above, but thestarted andfinished 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 stuffduration=finished-started# 1560979.429234 - 1560978.425334Rails.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 tomultiple events at once. Here's how to subscribe to everything fromActionController:
ActiveSupport::Notifications.subscribe(/action_controller/)do|event|# inspect all ActionController eventsend
Whether delivery of this message is performed or not
{mailer:"Notification",message_id:"4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",subject:"Rails Guides",to:["users@rails.com","dhh@rails.com"],from:["me@rails.com"],date:Sat,10Mar201214:18:09+0100,mail:"...",# omitted for brevityperform_deliveries:true}
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 transactionuntil needed:
ActiveRecord::Base.transactiondo# 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.transactiondo|t1|User.count# Fires an event for t1.ActiveRecord::Base.transactiondo|t2|# The next line fires no event for t2, because the only# real database transaction in this example is t1.User.first.touchendend
However, ifrequires_new: true is passed, you get an event for the nestedtransaction too. This might be a savepoint under the hood:
ActiveRecord::Base.transactiondo|t1|User.count# Fires an event for t1.ActiveRecord::Base.transaction(requires_new:true)do|t2|User.first.touch# Fires an event for t2.endend
This event is emitted when a database transaction finishes. The state of thetransaction 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 behelpful for tracing database activity. For example, by trackingtransaction.uuid.
This event is emitted when a deprecated association is accessed, and theconfigured deprecated associations mode is:notify.
Key
Value
:reflection
The reflection of the association
:message
A descriptive message about the access
:location
The application-level location of the access
:backtrace
Only present if the option:backtrace is true
The:location is aThread::Backtrace::Location object, and:backtrace, ifpresent, is an array ofThread::Backtrace::Location objects. These arecomputed using the Active Record backtrace cleaner. In Rails applications, thisis the same asRails.backtrace_cleaner.
Adding your own events is easy as well. Active Support will take care ofall the heavy lifting for you. Simply callActiveSupport::Notifications.instrument with aname,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 theinstrument call will makeit into the payload.
Here's an example:
ActiveSupport::Notifications.instrument"my.custom.event",this: :datado# do your custom stuff hereend
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 namedtweet.twitter.