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/
Добавить комментарий