Инструментарий Active Support

Active Support — часть ядра Rails, которая предоставляет расширение языка Ruby, утилиты и другие возможности. Она включает инструментарий API, который может использоваться внутри приложения, для отслеживания определенных действий, которые возникают как в коде Ruby, так и внутри приложения Rails и самого фреймворка. Однако, она не ограничена Rails. При необходимости ее можно независимо использовать в других скриптах Ruby если вы желаете.

В этом руководстве вы научитесь использовать API инструментария Active Support для отслеживания событий внутри Rails или другого Ruby-кода.

После прочтения данного руководства вы будете знать:

  • Какой инструментарий предоставляется.
  • Как добавить подписчика к хуку.
  • Как увидеть тайминги от инструментария в браузере.
  • Какие есть хуки внутри фреймворка Rails для инструментария.
  • Как создать произвольную реализацию инструментария.

1. Введение в инструментарий

Инструментарий API, предоставленный Active Support, позволяет разработчикам создавать хуки, которыми могут пользоваться другие разработчики. Некоторые из них присутствуют в фреймворке Rails. С этим API, разработчики могут быть оповещены при возникновении определенного события в их приложении или другом коде Ruby.

Например, есть хук внутри Active Record который вызывается каждый раз когда Active Record использует запрос SQL к базе данных. На этот хук можно подписаться и использовать его для отслеживания количества запросов в течении определенного экшна. Есть другой хук, оборачивающий экшны контроллеров. Он может быть использован, например, для отслеживания, как долго выполнялся определенный экшн.

Вы даже можете создать свои собственные события внутри приложения, на которые вы потом сможете подписаться.

2. Подписка на события

Подписаться на событие просто. Используйте 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

3. Просмотр таймингов от инструментария в браузере

Rails реализует стандарт Server Timing, чтобы сделать информацию о тайминге доступной в веб-браузере. Чтобы включить, отредактируйте конфигурацию среды (обычно development.rb, так как это используется в основном в development), чтобы включить следующее:

  config.server_timing = true

Как только настроено (включая перезагрузку вашего сервера), можно пойти в панель Developer Tools вашего браузера, затем выбрать Network и перезагрузить вашу страницу. Затем можно выбрать любой запрос к серверу Rails, и увидеть тайминги сервера во вкладке таймингов. Пример этого можно увидеть в документации Firefox.

4. Хуки фреймворка Rails

Внутри фреймворка Ruby on Rails присутствует множество хуков для обычных событий. Эти события и их нагрузка описываются ниже.

4.1. Action Controller

4.1.1. start_processing.action_controller
Ключ Значение
: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"
}
4.1.2. process_action.action_controller
Ключ Значение
: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
}
4.1.3. send_file.action_controller
Ключ Значение
:path Полный путь к файлу

Дополнительные ключи могут быть добавлены при вызове.

4.1.4. send_data.action_controller

ActionController не добавляет какой-либо конкретной информации при загрузке. Все опции передаются через полезную нагрузку (payload).

4.1.5. redirect_to.action_controller
Ключ Значение
:status Код HTTP ответа
:location URL для переадресации
:request Объект ActionDispatch::Request
{
  status: 302,
  location: "http://localhost:3000/posts/new",
  request: <ActionDispatch::Request:0x00007ff1cb9bd7b8>
}
4.1.6. halted_callback.action_controller
Ключ Значение
:filter Фильтр, прервавший экшн
{
  filter: ":halting_filter"
}
4.1.7. unpermitted_parameters.action_controller
Ключ Значение
:keys Неразрешенные ключи
:context Хэш со следующими ключами: :controller, :action, :params, :request

4.2. Action Controller — кэширование

4.2.1. write_fragment.action_controller
Ключ Значение
:key Полный ключ
{
  key: 'posts/1-dashboard-view'
}
4.2.2. read_fragment.action_controller
Ключ Значение
:key Полный ключ
{
  key: 'posts/1-dashboard-view'
}
4.2.3. expire_fragment.action_controller
Ключ Значение
:key Полный ключ
{
  key: 'posts/1-dashboard-view'
}
4.2.4. exist_fragment?.action_controller
Ключ Значение
:key Полный ключ
{
  key: 'posts/1-dashboard-view'
}

4.3. Action Dispatch

4.3.1. process_middleware.action_dispatch
Ключ Значение
:middleware Имя промежуточной программы
4.3.2. redirect.action_dispatch
Ключ Значение
:status Код отклика HTTP
:location URL, куда перенаправить
:request Объект ActionDispatch::Request
4.3.3. request.action_dispatch
Ключ Значение
:request Объект ActionDispatch::Request

4.4. Action View

4.4.1. render_template.action_view
Ключ Значение
:identifier Полный путь до шаблона
:layout Применяемый макет
:locals Локальные переменные, переданные в шаблон
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
  layout: "layouts/application",
  locals: { foo: "bar" }
}
4.4.2. render_partial.action_view
Ключ Значение
:identifier Полный путь до шаблона
:locals Локальные переменные, переданные в шаблон
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
  locals: { foo: "bar" }
}
4.4.3. render_collection.action_view
Ключ Значение
:identifier Полный путь к шаблону
:count Размер коллекции
:cache_hits Количество партиалов, извлеченных из кэша

Ключ :cache_hits включен, только если коллекция рендерится с cached: true.

{
  identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb",
  count: 3,
  cache_hits: 0
}
4.4.4. render_layout.action_view
Ключ Значение
:identifier Полный путь к шаблону
{
  identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
}

4.5. Active Record

4.5.1. sql.active_record
Ключ Значение
: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
}
4.5.2. strict_loading_violation.active_record

Это событие выпускается, только когда config.active_record.action_on_strict_loading_violation установлен :log.

Ключ Значение
:owner Модель с включенным strict_loading
:reflection Отражение связи, которая пытается загрузиться
4.5.3. instantiation.active_record
Ключ Значение
:record_count Количество записей
:class_name Класс записи
{
  record_count: 1,
  class_name: "User"
}

4.6. Action Mailer

4.6.1. deliver.action_mailer
Ключ Значение
: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
}
4.6.2. process.action_mailer
Ключ Значение
:mailer Имя класса рассыльщика
:action Экшн
:args Аргументы
{
  mailer: "Notification",
  action: "welcome_email",
  args: []
}

4.7. Active Support - кэширование

4.7.1. cache_read.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
:hit Если это чтение успешно
:super_operation :fetch, когда чтение выполняется с fetch
4.7.2. cache_read_multi.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
:hit Если это чтение успешно
:super_operation :fetch_multi, когда чтение выполняется с fetch_multi
4.7.3. cache_generate.active_support

Это событие используется, только когда fetch вызывается с блоком.

Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища

Опции, переданные в fetch, будут объединены с полезной нагрузкой при записи в хранилище.

{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}
4.7.4. cache_fetch_hit.active_support

Это событие используется только когда fetch вызывается с блоком.

Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища

Опции, переданные в fetch, будут объединены с полезной нагрузкой.

{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}
4.7.5. cache_write.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища

Хранилища кэша также могут добавлять свои собственные данные.

{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}
4.7.6. cache_write_multi.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
4.7.7. cache_increment.active_support

Это событие вызывается только при использовании MemCacheStore или RedisCacheStore.

Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
:amount Сумма увеличения
{
  key: "bottles-of-beer",
  store: "ActiveSupport::Cache::RedisCacheStore",
  amount: 99
}
4.7.8. cache_decrement.active_support

Это событие вызывается только при использовании хранилищ кэша Memcached или Redis.

Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
:amount Сумма уменьшения
{
  key: "bottles-of-beer",
  store: "ActiveSupport::Cache::RedisCacheStore",
  amount: 1
}
4.7.9. cache_delete.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}
4.7.10. cache_delete_multi.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
4.7.11. cache_delete_matched.active_support

Это событие вызывается только при использовании RedisCacheStore, FileStore или MemoryStore.

Ключ Значение
:key Используемый паттерн ключа
:store Имя класса хранилища
{
  key: "posts/*",
  store: "ActiveSupport::Cache::RedisCacheStore"
}
4.7.12. cache_cleanup.active_support

Это событие вызывается только при использовании MemoryStore.

Ключ Значение
:store Имя класса хранилища
:size Количество записей в кэше перед очисткой
{
  store: "ActiveSupport::Cache::MemoryStore",
  size: 9001
}
4.7.13. cache_prune.active_support

Это событие вызывается только при использовании MemoryStore.

Ключ Значение
:store Имя класса хранилища
:key Целевой размер (в байтах) для кэша
:from Размер (в байтах) кэша перед сокращением
{
  store: "ActiveSupport::Cache::MemoryStore",
  key: 5000,
  from: 9001
}
4.7.14. cache_exist?.active_support
Ключ Значение
:key Ключ, используемый при хранении
:store Имя класса хранилища
{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

4.8. Active Support — сообщения

4.8.1. message_serializer_fallback.active_support
Ключ Значение
:serializer Основной (предназначенный) сериализатор
:fallback Запасной (фактический) сериализатор
:serialized Сериализованная строка
:deserialized Десериализованное значение
{
  serializer: :json_allow_marshal,
  fallback: :marshal,
  serialized: "\x04\b{\x06I\"\nHello\x06:\x06ETI\"\nWorld\x06;\x00T",
  deserialized: { "Hello" => "World" },
}

4.9. Active Job

4.9.1. enqueue_at.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:job Объект задания
4.9.2. enqueue.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:job Объект задания
4.9.3. enqueue_retry.active_job
Ключ Значение
:job Объект задания
:adapter Объект QueueAdapter, обрабатывающий задание
:error Ошибка, вызвавшая повтор
:wait Задержка повтора
4.9.4. enqueue_all.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:jobs Массив объектов Job
4.9.5. perform_start.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:job Объект задания
4.9.6. perform.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:job Объект задания
:db_runtime Сколько затрачено на выполнение запросов в базу данных в миллисекундах
4.9.7. retry_stopped.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:job Объект задания
:error Ошибка, вызвавшая повтор
4.9.8. discard.active_job
Ключ Значение
:adapter Объект QueueAdapter, обрабатывающий задание
:job Объект задания
:error Ошибка, вызвавшая отказ

4.10. Action Cable

4.10.1. perform_action.action_cable
Ключ Значение
:channel_class Имя класса канала
:action Экшн
:data Данные хэша
4.10.2. transmit.action_cable
Ключ Значение
:channel_class Имя класса канала
:data Данные хэша
:via С помощью
4.10.3. transmit_subscription_confirmation.action_cable
Ключ Значение
:channel_class Имя класса канала
4.10.4. transmit_subscription_rejection.action_cable
Ключ Значение
:channel_class Имя класса канала
4.10.5. broadcast.action_cable
Ключ Значение
:broadcasting Имя трансляции
:message Сообщение хэша
:coder Кодировщик

4.11. Active Storage

4.11.1. preview.active_storage
Ключ Значение
:key Токен безопасности
4.11.2. transform.active_storage
4.11.3. analyze.active_storage
Ключ Значение
:analyzer Имя анализатора, например ffprobe

4.12. Active Storage — сервис хранения

4.12.1. service_upload.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
:checksum Контрольная сумма для обеспечения целостности
4.12.2. service_streaming_download.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
4.12.3. service_download.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
4.12.4. service_download_chunk.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
:range Диапазон битов к прочтению
4.12.5. service_delete.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
4.12.6. service_delete_prefixed.active_storage
Ключ Значение
:prefix Префикс ключа
:service Имя сервиса
4.12.7. service_exist.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
:exist Существует или же нет файл или blob
4.12.8. service_url.active_storage
Ключ Значение
:key Токен безопасности
:service Имя сервиса
:url Сгенерированный URL
4.12.9. service_update_metadata.active_storage

Это событие вызывается только при использовании сервиса Google Cloud Storage.

Ключ Значение
:key Токен безопасности
:service Имя сервиса
:content_type Поле HTTP Content-Type
:disposition Поле HTTP Content-Disposition

4.13. Action Mailbox

4.13.1. process.action_mailbox
Ключ Значение
:mailbox Экземпляр класс Mailbox, унаследованного от ActionMailbox::Base
:inbound_email Хэш с данными о входящем письме, которое обрабатывается
{
  mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
  inbound_email: {
    id: 1,
    message_id: "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com",
    status: "processing"
  }
}

4.14. Railties

4.14.1. load_config_initializer.railties
Ключ Значение
:initializer Путь к загруженному инициализатору в config/initializers

4.15. Rails

4.15.1. deprecation.rails
Ключ Значение
:message Предупреждение устаревания
:callstack Откуда предупреждение пришло
:gem_name Название гема, отчитывающегося об устаревания
:deprecation_horizon Версия, в которой устаревшее поведение будет убрано

5. Исключения

Если происходит исключение во время любого инструментария, полезная нагрузка будет включать информацию о нем.

Ключ Значение
:exception Массив из двух элементов. Имя класса исключение и сообщение
:exception_object Объект исключения

6. Создание пользовательского события

Добавить свои события очень просто. 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.