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 тоже ничего не знает о моей библиотеке, но тем не менее мой гем подключен к общей системе логгирования.
Естественно, что логгирование это не единственная область применения уведомлений, но на этом примере проще показать, то зачем они нужны.
Автор: undr