Active Support — часть ядра Rails, которая предоставляет расширение языка Ruby, утилиты и другие возможности. Она включает инструментарий API, который может использоваться внутри приложения, для отслеживания определенных действий, которые возникают как в коде Ruby, так и внутри приложения Rails и самого фреймворка. Однако, она не ограничена Rails. При необходимости ее можно независимо использовать в других скриптах Ruby если вы желаете.
В этом руководстве вы научитесь использовать API инструментария Active Support для отслеживания событий внутри Rails или другого Ruby-кода.
После прочтения данного руководства вы будете знать:
- Какой инструментарий предоставляется.
- Как добавить подписчика к хуку.
- Как увидеть тайминги от инструментария в браузере.
- Какие есть хуки внутри фреймворка Rails для инструментария.
- Как создать произвольную реализацию инструментария.
Инструментарий API, предоставленный Active Support, позволяет разработчикам создавать хуки, которыми могут пользоваться другие разработчики. Некоторые из них присутствуют в фреймворке Rails. С этим API, разработчики могут быть оповещены при возникновении определенного события в их приложении или другом коде Ruby.
Например, есть хук внутри Active Record который вызывается каждый раз когда Active Record использует запрос SQL к базе данных. На этот хук можно подписаться и использовать его для отслеживания количества запросов в течении определенного экшна. Есть другой хук, оборачивающий экшны контроллеров. Он может быть использован, например, для отслеживания, как долго выполнялся определенный экшн.
Вы даже можете создать свои собственные события внутри приложения, на которые вы потом сможете подписаться.
Подписаться на событие просто. Используйте ActiveSupport::Notifications.subscribe
с блоком, чтобы слушать любое уведомление.
Блок получает следующие аргументы:
- Имя события
- Время начала
- Время окончания
- Уникальный ID для инструментария, запустившего это событие
- Полезная нагрузка для события
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
# ваш собственный код
Rails.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
Если вы беспокоитесь об аккуратности started
и finished
для вычисления точного прошедшего времени, используйте ActiveSupport::Notifications.monotonic_subscribe
. Преданный блок получает те же аргументы, что и предыдущий, но started
и finished
получит значения более аккуратного монотонного времени вместо секундного дискретного времени.
ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
# ваш собственный код
Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received (started: 1560978.425334, finished: 1560979.429234)
end
Определение всех этих аргументов блока каждый раз может быть утомительно. Можно легко создать ActiveSupport::Notifications::Event
из блока аргументов, например:
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
event.name # => "process_action.action_controller"
event.duration # => 10 (in milliseconds)
event.payload # => {:extra=>information}
Rails.logger.info "#{event} Received!"
end
Вы можете также подписаться на события, соответствующие регулярному выражению. Это позволит вам подписаться на несколько событий за раз. Вот как можно подписаться на все события ActionController
:
ActiveSupport::Notifications.subscribe(/action_controller/) do |*args|
# Проверка всех событий ActionController
end
Rails реализует стандарт Server Timing, чтобы сделать информацию о тайминге доступной в веб-браузере. Чтобы включить, отредактируйте конфигурацию среды (обычно development.rb
, так как это используется в основном в development), чтобы включить следующее:
config.server_timing = true
Как только настроено (включая перезагрузку вашего сервера), можно пойти в панель Developer Tools вашего браузера, затем выбрать Network и перезагрузить вашу страницу. Затем можно выбрать любой запрос к серверу Rails, и увидеть тайминги сервера во вкладке таймингов. Пример этого можно увидеть в документации Firefox.
Внутри фреймворка Ruby on Rails присутствует множество хуков для обычных событий. Эти события и их нагрузка описываются ниже.
Ключ |
Значение |
:controller |
Имя контроллера |
:action |
Экшн |
:params |
Хэш параметров запроса без фильтрации параметров |
:headers |
Заголовки запроса |
:format |
html/js/json/xml и.т.д. |
:method |
Метод HTTP-запроса |
:path |
Путь запроса |
{
controller: "PostsController",
action: "new",
params: { "action" => "new", "controller" => "posts" },
headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
format: :html,
method: "GET",
path: "/posts/new"
}
Ключ |
Значение |
:controller |
Имя контроллера |
:action |
Экшн |
:params |
Хэш параметров запроса без фильтрации параметров |
:headers |
Заголовки запроса |
:format |
html/js/json/xml и.т.д. |
:method |
Метод HTTP-запроса |
:path |
Путь запроса |
:request |
Объект ActionDispatch::Request |
:response |
Объект ActionDispatch::Response |
:status |
Код статуса HTTP |
:view_runtime |
Количество времени, потраченного во вью |
:db_runtime |
Время, потраченное на выполнение запросов к БД в мс |
{
controller: "PostsController",
action: "index",
params: {"action" => "index", "controller" => "posts"},
headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
format: :html,
method: "GET",
path: "/posts",
request: #<ActionDispatch::Request:0x00007ff1cb9bd7b8>,
response: #<ActionDispatch::Response:0x00007f8521841ec8>,
status: 200,
view_runtime: 46.848,
db_runtime: 0.157
}
Ключ |
Значение |
:path |
Полный путь к файлу |
Дополнительные ключи могут быть добавлены при вызове.
ActionController
не добавляет какой-либо конкретной информации при загрузке. Все опции передаются через полезную нагрузку (payload).
{
status: 302,
location: "http://localhost:3000/posts/new",
request: <ActionDispatch::Request:0x00007ff1cb9bd7b8>
}
Ключ |
Значение |
:filter |
Фильтр, прервавший экшн |
{
filter: ":halting_filter"
}
Ключ |
Значение |
:keys |
Неразрешенные ключи |
:context |
Хэш со следующими ключами: :controller , :action , :params , :request |
Ключ |
Значение |
:key |
Полный ключ |
{
key: 'posts/1-dashboard-view'
}
Ключ |
Значение |
:key |
Полный ключ |
{
key: 'posts/1-dashboard-view'
}
Ключ |
Значение |
:key |
Полный ключ |
{
key: 'posts/1-dashboard-view'
}
Ключ |
Значение |
:key |
Полный ключ |
{
key: 'posts/1-dashboard-view'
}
Ключ |
Значение |
:middleware |
Имя промежуточной программы |
Ключ |
Значение |
:identifier |
Полный путь до шаблона |
:layout |
Применяемый макет |
:locals |
Локальные переменные, переданные в шаблон |
{
identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
layout: "layouts/application",
locals: { foo: "bar" }
}
Ключ |
Значение |
:identifier |
Полный путь до шаблона |
:locals |
Локальные переменные, переданные в шаблон |
{
identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
locals: { foo: "bar" }
}
Ключ |
Значение |
:identifier |
Полный путь к шаблону |
:count |
Размер коллекции |
:cache_hits |
Количество партиалов, извлеченных из кэша |
Ключ :cache_hits
включен, только если коллекция рендерится с cached: true
.
{
identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb",
count: 3,
cache_hits: 0
}
Ключ |
Значение |
:identifier |
Полный путь к шаблону |
{
identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
}
Ключ |
Значение |
:sql |
Выражение SQL |
:name |
Имя операции |
:connection |
Объект соединения |
:binds |
Связанные параметры |
:type_casted_binds |
Приведенные связанные параметры |
:statement_name |
Имя выражения SQL |
:cached |
true если использованы кэшированные запросы |
Адаптеры могут добавлять свои собственные данные.
{
sql: "SELECT \"posts\".* FROM \"posts\" ",
name: "Post Load",
connection: <ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>,
binds: [<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
type_casted_binds: [11],
statement_name: nil
}
Это событие выпускается, только когда config.active_record.action_on_strict_loading_violation
установлен :log
.
Ключ |
Значение |
:owner |
Модель с включенным strict_loading |
:reflection |
Отражение связи, которая пытается загрузиться |
Ключ |
Значение |
:record_count |
Количество записей |
:class_name |
Класс записи |
{
record_count: 1,
class_name: "User"
}
Ключ |
Значение |
:mailer |
Имя класса рассыльщика |
:message_id |
ID сообщения, создается Mail гемом |
:subject |
Тема сообщения |
:to |
Адресат(ы) сообщения |
:from |
Отправитель сообщения |
:bcc |
BCC адреса сообщения |
:cc |
CC адреса сообщения |
:date |
Дата сообщения |
:mail |
Кодированная форма сообщения |
:perform_deliveries |
Была ли вызвана доставка этого сообщения или нет |
{
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, 10 Mar 2012 14:18:09 +0100,
mail: "...", # опущено для краткости
perform_deliveries: true
}
Ключ |
Значение |
:mailer |
Имя класса рассыльщика |
:action |
Экшн |
:args |
Аргументы |
{
mailer: "Notification",
action: "welcome_email",
args: []
}
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
:hit |
Если это чтение успешно |
:super_operation |
:fetch , когда чтение выполняется с fetch |
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
:hit |
Если это чтение успешно |
:super_operation |
:fetch_multi , когда чтение выполняется с fetch_multi |
Это событие используется, только когда fetch
вызывается с блоком.
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
Опции, переданные в fetch
, будут объединены с полезной нагрузкой при записи в хранилище.
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
Это событие используется только когда fetch
вызывается с блоком.
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
Опции, переданные в fetch
, будут объединены с полезной нагрузкой.
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
Хранилища кэша также могут добавлять свои собственные данные.
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
Это событие вызывается только при использовании MemCacheStore
или RedisCacheStore
.
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
:amount |
Сумма увеличения |
{
key: "bottles-of-beer",
store: "ActiveSupport::Cache::RedisCacheStore",
amount: 99
}
Это событие вызывается только при использовании хранилищ кэша Memcached или Redis.
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
:amount |
Сумма уменьшения |
{
key: "bottles-of-beer",
store: "ActiveSupport::Cache::RedisCacheStore",
amount: 1
}
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
Это событие вызывается только при использовании RedisCacheStore
, FileStore
или MemoryStore
.
Ключ |
Значение |
:key |
Используемый паттерн ключа |
:store |
Имя класса хранилища |
{
key: "posts/*",
store: "ActiveSupport::Cache::RedisCacheStore"
}
Это событие вызывается только при использовании MemoryStore
.
Ключ |
Значение |
:store |
Имя класса хранилища |
:size |
Количество записей в кэше перед очисткой |
{
store: "ActiveSupport::Cache::MemoryStore",
size: 9001
}
Это событие вызывается только при использовании MemoryStore
.
Ключ |
Значение |
:store |
Имя класса хранилища |
:key |
Целевой размер (в байтах) для кэша |
:from |
Размер (в байтах) кэша перед сокращением |
{
store: "ActiveSupport::Cache::MemoryStore",
key: 5000,
from: 9001
}
Ключ |
Значение |
:key |
Ключ, используемый при хранении |
:store |
Имя класса хранилища |
{
key: "name-of-complicated-computation",
store: "ActiveSupport::Cache::MemCacheStore"
}
Ключ |
Значение |
:serializer |
Основной (предназначенный) сериализатор |
:fallback |
Запасной (фактический) сериализатор |
:serialized |
Сериализованная строка |
:deserialized |
Десериализованное значение |
{
serializer: :json_allow_marshal,
fallback: :marshal,
serialized: "\x04\b{\x06I\"\nHello\x06:\x06ETI\"\nWorld\x06;\x00T",
deserialized: { "Hello" => "World" },
}
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:job |
Объект задания |
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:job |
Объект задания |
Ключ |
Значение |
:job |
Объект задания |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:error |
Ошибка, вызвавшая повтор |
:wait |
Задержка повтора |
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:jobs |
Массив объектов Job |
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:job |
Объект задания |
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:job |
Объект задания |
:db_runtime |
Сколько затрачено на выполнение запросов в базу данных в миллисекундах |
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:job |
Объект задания |
:error |
Ошибка, вызвавшая повтор |
Ключ |
Значение |
:adapter |
Объект QueueAdapter, обрабатывающий задание |
:job |
Объект задания |
:error |
Ошибка, вызвавшая отказ |
Ключ |
Значение |
:channel_class |
Имя класса канала |
:action |
Экшн |
:data |
Данные хэша |
Ключ |
Значение |
:channel_class |
Имя класса канала |
:data |
Данные хэша |
:via |
С помощью |
Ключ |
Значение |
:channel_class |
Имя класса канала |
Ключ |
Значение |
:channel_class |
Имя класса канала |
Ключ |
Значение |
:broadcasting |
Имя трансляции |
:message |
Сообщение хэша |
:coder |
Кодировщик |
Ключ |
Значение |
:key |
Токен безопасности |
Ключ |
Значение |
:analyzer |
Имя анализатора, например ffprobe |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
:checksum |
Контрольная сумма для обеспечения целостности |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
:range |
Диапазон битов к прочтению |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
Ключ |
Значение |
:prefix |
Префикс ключа |
:service |
Имя сервиса |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
:exist |
Существует или же нет файл или blob |
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
:url |
Сгенерированный URL |
Это событие вызывается только при использовании сервиса Google Cloud Storage.
Ключ |
Значение |
:key |
Токен безопасности |
:service |
Имя сервиса |
:content_type |
Поле HTTP Content-Type |
:disposition |
Поле HTTP Content-Disposition |
Ключ |
Значение |
:mailbox |
Экземпляр класс Mailbox, унаследованного от ActionMailbox::Base |
:inbound_email |
Хэш с данными о входящем письме, которое обрабатывается |
{
mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
inbound_email: {
id: 1,
message_id: "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com",
status: "processing"
}
}
Ключ |
Значение |
:initializer |
Путь к загруженному инициализатору в config/initializers |
Ключ |
Значение |
:message |
Предупреждение устаревания |
:callstack |
Откуда предупреждение пришло |
:gem_name |
Название гема, отчитывающегося об устаревания |
:deprecation_horizon |
Версия, в которой устаревшее поведение будет убрано |
Если происходит исключение во время любого инструментария, полезная нагрузка будет включать информацию о нем.
Ключ |
Значение |
:exception |
Массив из двух элементов. Имя класса исключение и сообщение |
:exception_object |
Объект исключения |
Добавить свои события очень просто. Active Support будет делать всю тяжелую работу за вас. Просто вызовите ActiveSupport::Notifications.instrument
с name
, payload
и блоком. Уведомление будет отправлено после возвращения блока. Active Support сгенерирует время старта и окончания и добавит уникальный ID инструментария. Все данные переданные в вызов instrument
будут выполнены в полезной нагрузке.
Пример:
ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
# Создание ваших пользовательских настроек тут
end
Теперь можно слушать это событие:
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # {:this=>:data}
end
Также можно вызвать instrument
без передачи блока. Это позволяет использовать инфраструктуру инструментария для других применений (обмен сообщениями).
ActiveSupport::Notifications.instrument "my.custom.event", this: :data
ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
puts data.inspect # {:this=>:data}
end
Вы должны следовать соглашениям Rails при создании своих событий. Формат: event.library
. Если ваше приложение отправляет Tweets, вы должны назвать событие tweet.twitter
.