📜 ⬆️ ⬇️

What is ActiveSupport :: Notifications and why?

ActiveSupport::Notifications is a notification system built into the rails. You can subscribe to certain notifications in Rails and call your code when they are sent. This is somewhat similar to ActiveSupport::Callbacks , but they work throughout the project and no events need to be announced in advance.



For example, we can subscribe to a 'render' notification:
')
 ActiveSupport::Notifications.subscribe("render") do |*args| #        render end 


You can use a regular expression as the name of the notification, then you subscribe to all notifications matching the expression. If you want to subscribe to all notifications, just don’t pass anything to the subscribe method. The subscribe method returns a link to the subscriber, it may be required to unsubscribe from the notification.

You can send a notification using the ActiveSupport::Notifications.publish method:

 ActiveSupport::Notifications.publish('render', 'arg1', 'arg2') 


'render' , 'arg1' and 'arg2' will be transferred to the subscribe block.

I haven’t found any code in Rails that directly uses these features, probably, this feature is supposed to be used by users of the framework for applied tasks. But in ActiveSupport::Notifications there is a very useful method instrument . It accepts a block and after which it sends a notification with time stamps of the beginning and end of block execution, as well as a hash with additional data. Rails uses this mechanism, and indirectly, both the subscribe and publish methods to create detailed logs in the development environment (production uses the same “gauges”, but not everything is written to the log)

For the same purposes, these “gauges” can be used in your application. You can wrap the code that you want to check in this method and write the result of the execution to the log. For example, we have a method whose execution speed we would like to track.

 def do_something #    end 


Just wrap it in block ActiveSupport::Notifications.instrument

 def do_something ActiveSupport::Notifications.instrument('benchmark.do_something', desc: 'Some description') do #    end end 


In any place, for example, config/initializers/benchmarking.rb subscribe to all notifications with the string 'benchmark.' .

 logger = Logger.new(File.join(Rails.root, 'log', "benchmarks.log")) ActiveSupport::Notifications.subscribe(%r/benchmark\.*/) do |name, start, ending, transaction_id, payload| method = name.split(?.).last duration = (1000.0 * (ending - start)) message = if payload[:exception].present? payload[:exception].join(' ') else payload[:desc].to_s end logger.info("Benchmark:%s: %.0fms - %s" % method, duration, message) end 


The following variables will be passed to the block: name, start, ending, transaction_id, payload .



Then just write the execution time in the log. If an exception occurs in payload[:exception] will be written with the name of the exception and an error message. This also needs to be taken into account.

The role of ActiveSupport::Notifications in logging Rails can be found in more detail in the modules ActiveSupport::LogSubscriber , ActiveRecord::LogSubscriber , ActionController::LogSubscriber and ActionMailer::LogSubscriber .

There is another method that temporarily subscribes to notifications, but only as long as the block passed to it is executed.

 callback = lambda do|*args| #        #  "event.name" end ActiveSupport::Notifications.subscribed(callback, "event.name") do #        end 


To unsubscribe from an event, call the unsubscribe method and pass it a link to the subscriber.

 ActiveSupport::Notifications.unsubscribe(subscriber) 


The notification mechanism itself is hidden in the ActiveSupport::Notifications::Fanout , it will also be interesting to look at the ActiveSupport::Notifications::Instrumenter class, which is responsible for measuring the block execution time in the instrument method

As an example, a monkipatch for real-time counting the execution time of methods using ActiveSupport::Notifications :

 class Module def benchmark_it *names options, names = benchmark_options_and_names(*names) names.each do |name| target, punctuation = name.to_s.sub(/([?!=])$/, ''), $1 define_method "#{target}_with_benchmark#{punctuation}" do |*args| ActiveSupport::Notifications.instrument("benchmark.#{self.name}.#{name}", options) do send("#{target}_without_benchmark#{punctuation}", *args) end end alias_method_chain name, :benchmark end end protected def benchmark_options_and_names *args options = args.last.is_a?(Hash) ? args.pop : {} [{desc: ''}.merge(options), args] end end ActiveSupport::Notifications.subscribe(%r/benchmark\.*/) do |name, start, ending, transaction_id, payload| _, classname, method = name.split(?.) duration = (1000.0 * (ending - start)) message = if payload[:exception].present? payload[:exception].join(' ') else payload[:desc].to_s end Rails.logger.info("Benchmark: %s.%s: %.0fms - %s" % classname, method, duration, message) end 


Used as follows:

 class MyClass def my_method #   - end #       benchmark_it :my_method, desc: '  ' end 


What is all this for? Using the example of the same Rails, you can see that if your code consists of various loosely coupled components, then you can interact with them using such notifications. Accordingly, I can write my ORM or some other library, for example MyORM , and hang the logging task on the MyORM::LogSubscriber class inherited from ActiveSupport::LogSubscriber . The message code responsible for displaying the logs is not spread over the application, but in one place. Well, of course, you need to place the sensors throughout the library. By the way, these same sensors can be used for anything else besides logging.

On the one hand, my code is not tied to Rails, on the other, Rails also does not know anything about my library, but nevertheless my hem is connected to a common logging system.

Naturally, logging is not the only application of notifications, but in this example it is easier to show, then why are they needed.

Source: https://habr.com/ru/post/160701/


All Articles