Что такое ActiveSupport::Notifications и зачем нужны?

от автора

ActiveSupport::Notifications – это встроенная в рельсы система уведомлений. Вы можете подписаться на определенные уведомления в Rails и вызывать свой код когда они будут посланы. Это чем-то похоже на ActiveSupport::Callbacks, но работают во всем проекте и события не нужно заранее объявлять.

К примеру, мы можем подписаться на уведомление 'render':

ActiveSupport::Notifications.subscribe("render") do |*args|   # Этот блок будет вызван при получении уведомления render end 

Вы можете использовать регулярное выражение в качестве имени уведомления, тогда вы подпишетесь на все уведомления подходящие под выражение. Если хотите подписаться на все уведомления, просто ничего не передавайте в метод subscribe. Метод subscribe возвращает ссылку на подписчика, она может потребоваться для отписки от уведомления.

Послать уведомление можно методом ActiveSupport::Notifications.publish:

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

В блок subscribe будет переданы 'render', 'arg1' и 'arg2'.

Я не нашел в Rails кода который напрямую использует эти возможности, вероятно, данный фунцианал предпологается использовать пользователям фреймворка для прикладных задач. Но в ActiveSupport::Notifications есть весьма полезный метод instrument. Он принимает блок и после выполнения которого отправляет уведомление с временными метками начала и конца выполнения блока, а так же хэш с дополнительными данными. Rails использует этот механизм, а опосредованно, и методы subscribe и publish, для создания подробных логов в девелоперском окружении (на продакшене используется те же “измерители”, но не все пишется в лог)

Для тех же целей эти “измерители” можно использовать в своем приложении. Можно обернуть код который вы хотите проверить в этот метод и результат выполнения писать в лог. Например, у нас есть метод скорость выполнения которого нам бы хотелось отслеживать.

def do_something   # Тут наши вычисления end 

Просто оборачиваем его в блок ActiveSupport::Notifications.instrument

def do_something   ActiveSupport::Notifications.instrument('benchmark.do_something', desc: 'Some description') do     # Тут наши вычисления   end end 

В каком-нибудь месте, например, config/initializers/benchmarking.rb подписываемся на все уведомления с строкой '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 

В блок будут переданы следующие переменные: name, start, ending, transaction_id, payload.

  • name – имя пойманного уведомления
  • start – время начала выполнения блока
  • ending – время конца выполнения блока
  • transaction_id – уникальный id, как правило уникальный в пределах одного треда
  • payload – дополнительные данные переданные в “измеритель”

Далее просто записываем время исполнения в лог. При возникновении исключительной ситуации в payload[:exception] будет записан масив с именем исключения и сообщением об ошибке. Это тоже нужно учесть.

Более подробно роль ActiveSupport::Notifications в логгировании Rails можно посмотреть в модулях ActiveSupport::LogSubscriber, ActiveRecord::LogSubscriber, ActionController::LogSubscriber и ActionMailer::LogSubscriber.

Есть еще один метод, который временно подписывается на уведомления, но только пока выполняется блок переданный ему.

callback = lambda do|*args|   # Это блок который выполнится в момент посылки    # уведомления "event.name" end  ActiveSupport::Notifications.subscribed(callback, "event.name") do   # Блок в течении которого подписка будет действительна end 

Чтобы отписаться от события вызовите метод unsubscribe и передайте в него ссылку на подписчика.

ActiveSupport::Notifications.unsubscribe(subscriber) 

Сам механизм рассылки уведомлений скрыт в классе ActiveSupport::Notifications::Fanout, так же будет интересно посмотреть на класс ActiveSupport::Notifications::Instrumenter, который отвечает за измерение времени выполнения блока в методе instrument

В качестве примера, монкипатч для реалтаймового подсчета времени выполнения методов, использующий 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 

Используется так:

class MyClass   def my_method     # Делаем тут чего-нибудь   end      # Указываем что хотим протестить этот метод   benchmark_it :my_method, desc: 'Сообщение для логгера' end 

Для чего все это нужно? На примере тех же Rails видно, что если Ваш код состоит из разных слабо связанных компонентов, то можно наладить взаимодействие с ними с помощью таких уведомлений. Соответственно, я могу написать свою ORM или еще какую-нибудь библиотеку, к примеру MyORM, и задачу логгирования повесить на класс MyORM::LogSubscriber отнаследованный от ActiveSupport::LogSubscriber. Весть код, отвечающий за отображение логов не размазан по приложению, а находится в одном месте. Ну, естественно, нужно расставить датчики по всей библеотеке. Кстати, эти же самые датчики можно использовать для чего угодно еще помимо логгирования.

С одной стороны мой код не завязан на Rails, с другой, Rails тоже ничего не знает о моей библиотеке, но тем не менее мой гем подключен к общей системе логгирования.

Естественно, что логгирование это не единственная область применения уведомлений, но на этом примере проще показать, то зачем они нужны.

ссылка на оригинал статьи http://habrahabr.ru/post/160701/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *