Instrumentación de Active Support

Active Support es una parte del núcleo de Rails que proporciona extensiones del lenguaje Ruby, utilidades y otras cosas. Una de las cosas que incluye es una API de instrumentación que se puede usar dentro de una aplicación para medir ciertas acciones que ocurren dentro del código Ruby, como las que están dentro de una aplicación Rails o el propio framework. Sin embargo, no está limitado a Rails. Se puede usar de manera independiente en otros scripts Ruby si se desea.

En esta guía, aprenderás cómo usar la API de instrumentación de Active Support para medir eventos dentro de Rails y otro código Ruby.

Después de leer esta guía, sabrás:


1 Introducción a la Instrumentación

La API de instrumentación proporcionada por Active Support permite a los desarrolladores proporcionar hooks a los que otros desarrolladores pueden engancharse. Hay varios de estos dentro del framework Rails. Con esta API, los desarrolladores pueden optar por ser notificados cuando ocurren ciertos eventos dentro de su aplicación u otra pieza de código Ruby.

Por ejemplo, hay un hook proporcionado dentro de Active Record que se llama cada vez que Active Record utiliza una consulta SQL en una base de datos. Este hook podría suscribirse y usarse para rastrear el número de consultas durante una cierta acción. Hay otro hook alrededor del procesamiento de una acción de un controlador. Esto podría usarse, por ejemplo, para rastrear cuánto tiempo ha tomado una acción específica.

Incluso puedes crear tus propios eventos dentro de tu aplicación a los que luego puedes suscribirte.

2 Suscribirse a un Evento

Usa ActiveSupport::Notifications.subscribe con un bloque para escuchar cualquier notificación. Dependiendo de la cantidad de argumentos que tome el bloque, recibirás diferentes datos.

La primera forma de suscribirse a un evento es usar un bloque con un solo argumento. El argumento será una instancia de ActiveSupport::Notifications::Event.

ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event|
  event.name        # => "process_action.action_controller"
  event.duration    # => 10 (en milisegundos)
  event.allocations # => 1826
  event.payload     # => {:extra=>information}

  Rails.logger.info "#{event} ¡Recibido!"
end

Si no necesitas todos los datos registrados por un objeto Event, también puedes especificar un bloque que tome los siguientes cinco argumentos:

  • Nombre del evento
  • Hora en que comenzó
  • Hora en que terminó
  • Un ID único para el instrumentador que activó el evento
  • La carga útil para el evento
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
  # tus propias cosas personalizadas
  Rails.logger.info "#{name} ¡Recibido! (comenzó: #{started}, terminó: #{finished})" # process_action.action_controller ¡Recibido! (comenzó: 2019-05-05 13:43:57 -0800, terminó: 2019-05-05 13:43:58 -0800)
end

Si te preocupa la precisión de started y finished para calcular un tiempo transcurrido preciso, entonces usa ActiveSupport::Notifications.monotonic_subscribe. El bloque dado recibiría los mismos argumentos que arriba, pero started y finished tendrán valores con un tiempo monotónico preciso en lugar de tiempo de reloj de pared.

ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
  # tus propias cosas personalizadas
  duration = finished - started # 1560979.429234 - 1560978.425334
  Rails.logger.info "#{name} ¡Recibido! (duración: #{duration})" # process_action.action_controller ¡Recibido! (duración: 1.0039)
end

También puedes suscribirte a eventos que coincidan con una expresión regular. Esto te permite suscribirte a múltiples eventos a la vez. Aquí tienes cómo suscribirte a todo desde ActionController:

ActiveSupport::Notifications.subscribe(/action_controller/) do |event|
  # inspecciona todos los eventos de ActionController
end

3 Hooks del Framework Rails

Dentro del framework Ruby on Rails, hay una serie de hooks proporcionados para eventos comunes. Estos eventos y sus cargas útiles se detallan a continuación.

3.1 Action Controller

3.1.1 start_processing.action_controller

Clave Valor
:controller El nombre del controlador
:action La acción
:request El objeto ActionDispatch::Request
:params Hash de parámetros de la solicitud sin ningún parámetro filtrado
:headers Encabezados de la solicitud
:format html/js/json/xml etc
:method Verbo de solicitud HTTP
:path Ruta de la solicitud
{
  controller: "PostsController",
  action: "new",
  params: { "action" => "new", "controller" => "posts" },
  headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
  format: :html,
  method: "GET",
  path: "/posts/new"
}

3.1.2 process_action.action_controller

Clave Valor
:controller El nombre del controlador
:action La acción
:params Hash de parámetros de la solicitud sin ningún parámetro filtrado
:headers Encabezados de la solicitud
:format html/js/json/xml etc
:method Verbo de solicitud HTTP
:path Ruta de la solicitud
:request El objeto ActionDispatch::Request
:response El objeto ActionDispatch::Response
:status Código de estado HTTP
:view_runtime Tiempo dedicado a la vista en ms
:db_runtime Tiempo dedicado a ejecutar consultas de base de datos en ms
{
  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
}

3.1.3 send_file.action_controller

Clave Valor
:path Ruta completa al archivo

Claves adicionales pueden ser agregadas por el que llama.

3.1.4 send_data.action_controller

ActionController no agrega información específica a la carga útil. Todas las opciones se pasan a la carga útil.

3.1.5 redirect_to.action_controller

Clave Valor
:status Código de respuesta HTTP
:location URL a la que redirigir
:request El objeto ActionDispatch::Request
{
  status: 302,
  location: "http://localhost:3000/posts/new",
  request: <ActionDispatch::Request:0x00007ff1cb9bd7b8>
}

3.1.6 halted_callback.action_controller

Clave Valor
:filter Filtro que detuvo la acción
{
  filter: ":halting_filter"
}

3.1.7 unpermitted_parameters.action_controller

Clave Valor
:keys Las claves no permitidas
:context Hash con las siguientes claves: :controller, :action, :params, :request

3.1.8 send_stream.action_controller

Clave Valor
:filename El nombre del archivo
:type Tipo de contenido HTTP
:disposition Disposición de contenido HTTP
{
  filename: "subscribers.csv",
  type: "text/csv",
  disposition: "attachment"
}

3.2 Action Controller: Caching

3.2.1 write_fragment.action_controller

Clave Valor
:key La clave completa
{
  key: 'posts/1-dashboard-view'
}

3.2.2 read_fragment.action_controller

Clave Valor
:key La clave completa
{
  key: 'posts/1-dashboard-view'
}

3.2.3 expire_fragment.action_controller

Clave Valor
:key La clave completa
{
  key: 'posts/1-dashboard-view'
}

3.2.4 exist_fragment?.action_controller

Clave Valor
:key La clave completa
{
  key: 'posts/1-dashboard-view'
}

3.3 Action Dispatch

3.3.1 process_middleware.action_dispatch

Clave Valor
:middleware Nombre del middleware

3.3.2 redirect.action_dispatch

Clave Valor
:status Código de respuesta HTTP
:location URL a la que redirigir
:request El objeto ActionDispatch::Request

3.3.3 request.action_dispatch

Clave Valor
:request El objeto ActionDispatch::Request

3.4 Action View

3.4.1 render_template.action_view

Clave Valor
:identifier Ruta completa al template
:layout Layout aplicable
:locals Variables locales pasadas al template
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
  layout: "layouts/application",
  locals: { foo: "bar" }
}

3.4.2 render_partial.action_view

Clave Valor
:identifier Ruta completa al template
:locals Variables locales pasadas al template
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
  locals: { foo: "bar" }
}

3.4.3 render_collection.action_view

Clave Valor
:identifier Ruta completa al template
:count Tamaño de la colección
:cache_hits Número de parciales obtenidos de la caché

La clave :cache_hits solo se incluye si la colección se renderiza con cached: true.

{
  identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb",
  count: 3,
  cache_hits: 0
}

3.4.4 render_layout.action_view

Clave Valor
:identifier Ruta completa al template
{
  identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
}

3.5 Active Record

3.5.1 sql.active_record

Clave Valor
:sql Sentencia SQL
:name Nombre de la operación
:connection Objeto de conexión
:transaction Transacción actual, si la hay
:binds Parámetros de enlace
:type_casted_binds Parámetros de enlace convertidos de tipo
:statement_name Nombre de la sentencia SQL
:async true si la consulta se carga de manera asincrónica
:cached true se añade cuando se usan consultas en caché
:row_count Número de filas devueltas por la consulta

Los adaptadores pueden agregar sus propios datos también.

{
  sql: "SELECT \"posts\".* FROM \"posts\" ",
  name: "Post Load",
  connection: <ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>,
  transaction: <ActiveRecord::ConnectionAdapters::RealTransaction:0x0000000121b5d3e0>
  binds: [<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
  type_casted_binds: [11],
  statement_name: nil,
  row_count: 5
}

Si la consulta no se ejecuta en el contexto de una transacción, :transaction es nil.

3.5.2 strict_loading_violation.active_record

Este evento solo se emite cuando config.active_record.action_on_strict_loading_violation está configurado en :log.

Clave Valor
:owner Modelo con strict_loading habilitado
:reflection Reflexión de la asociación que intentó cargar

3.5.3 instantiation.active_record

Clave Valor
:record_count Número de registros que se instanciaron
:class_name Clase del registro
{
  record_count: 1,
  class_name: "User"
}

3.5.4 start_transaction.active_record

Este evento se emite cuando se ha iniciado una transacción.

Clave Valor
:transaction Objeto de transacción
:connection Objeto de conexión

Por favor, ten en cuenta que Active Record no crea la transacción real de la base de datos hasta que es necesario:

ActiveRecord::Base.transaction do
  # Estamos dentro del bloque, pero no se ha activado ningún evento todavía.

  # La siguiente línea hace que Active Record inicie la transacción.
  User.count # Evento activado aquí.
end

Recuerda que las llamadas anidadas ordinarias no crean nuevas transacciones:

ActiveRecord::Base.transaction do |t1|
  User.count # Dispara un evento para t1.
  ActiveRecord::Base.transaction do |t2|
    # La siguiente línea no dispara un evento para t2, porque la única
    # transacción real de base de datos en este ejemplo es t1.
    User.first.touch
  end
end

Sin embargo, si se pasa requires_new: true, también obtienes un evento para la transacción anidada. Esto podría ser un punto de guardado bajo el capó:

ActiveRecord::Base.transaction do |t1|
  User.count # Dispara un evento para t1.
  ActiveRecord::Base.transaction(requires_new: true) do |t2|
    User.first.touch # Dispara un evento para t2.
  end
end

3.5.5 transaction.active_record

Este evento se emite cuando una transacción de base de datos termina. El estado de la transacción se puede encontrar en la clave :outcome.

Clave Valor
:transaction Objeto de transacción
:outcome :commit, :rollback, :restart, o :incomplete
:connection Objeto de conexión

En la práctica, no puedes hacer mucho con el objeto de transacción, pero aún puede ser útil para rastrear la actividad de la base de datos. Por ejemplo, rastreando transaction.uuid.

3.6 Action Mailer

3.6.1 deliver.action_mailer

Clave Valor
:mailer Nombre de la clase del mailer
:message_id ID del mensaje, generado por la gema Mail
:subject Asunto del correo
:to Dirección(es) de destino del correo
:from Dirección de origen del correo
:bcc Direcciones BCC del correo
:cc Direcciones CC del correo
:date Fecha del correo
:mail La forma codificada del correo
:perform_deliveries Si se realiza o no la entrega de este mensaje
{
  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: "...", # omitido por brevedad
  perform_deliveries: true
}

3.6.2 process.action_mailer

Clave Valor
:mailer Nombre de la clase del mailer
:action La acción
:args Los argumentos
{
  mailer: "Notification",
  action: "welcome_email",
  args: []
}

3.7 Active Support: Caching

3.7.1 cache_read.active_support

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén
:hit Si esta lectura es un acierto
:super_operation :fetch si una lectura se realiza con fetch

3.7.2 cache_read_multi.active_support

Clave Valor
:key Claves usadas en el almacén
:store Nombre de la clase del almacén
:hits Claves de aciertos de caché
:super_operation :fetch_multi si una lectura se realiza con fetch_multi

3.7.3 cache_generate.active_support

Este evento solo se emite cuando fetch se llama con un bloque.

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén

Las opciones pasadas a fetch se fusionarán con la carga útil al escribir en el almacén.

{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.7.4 cache_fetch_hit.active_support

Este evento solo se emite cuando fetch se llama con un bloque.

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén

Las opciones pasadas a fetch se fusionarán con la carga útil.

{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.7.5 cache_write.active_support

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén

Los almacenes de caché pueden agregar sus propios datos también.

{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.7.6 cache_write_multi.active_support

Clave Valor
:key Claves y valores escritos en el almacén
:store Nombre de la clase del almacén

3.7.7 cache_increment.active_support

Este evento solo se emite cuando se usa MemCacheStore o RedisCacheStore.

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén
:amount Cantidad de incremento
{
  key: "bottles-of-beer",
  store: "ActiveSupport::Cache::RedisCacheStore",
  amount: 99
}

3.7.8 cache_decrement.active_support

Este evento solo se emite cuando se usan los almacenes de caché Memcached o Redis.

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén
:amount Cantidad de decremento
{
  key: "bottles-of-beer",
  store: "ActiveSupport::Cache::RedisCacheStore",
  amount: 1
}

3.7.9 cache_delete.active_support

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén
{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.7.10 cache_delete_multi.active_support

Clave Valor
:key Claves usadas en el almacén
:store Nombre de la clase del almacén

3.7.11 cache_delete_matched.active_support

Este evento solo se emite cuando se usa RedisCacheStore, FileStore, o MemoryStore.

Clave Valor
:key Patrón de clave usado
:store Nombre de la clase del almacén
{
  key: "posts/*",
  store: "ActiveSupport::Cache::RedisCacheStore"
}

3.7.12 cache_cleanup.active_support

Este evento solo se emite cuando se usa MemoryStore.

Clave Valor
:store Nombre de la clase del almacén
:size Número de entradas en la caché antes de la limpieza
{
  store: "ActiveSupport::Cache::MemoryStore",
  size: 9001
}

3.7.13 cache_prune.active_support

Este evento solo se emite cuando se usa MemoryStore.

Clave Valor
:store Nombre de la clase del almacén
:key Tamaño objetivo (en bytes) para la caché
:from Tamaño (en bytes) de la caché antes de la poda
{
  store: "ActiveSupport::Cache::MemoryStore",
  key: 5000,
  from: 9001
}

3.7.14 cache_exist?.active_support

Clave Valor
:key Clave usada en el almacén
:store Nombre de la clase del almacén
{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.8 Active Support: Mensajes

3.8.1 message_serializer_fallback.active_support

Clave Valor
:serializer Serializador primario (previsto)
:fallback Serializador de respaldo (real)
:serialized Cadena serializada
:deserialized Valor deserializado
{
  serializer: :json_allow_marshal,
  fallback: :marshal,
  serialized: "\x04\b{\x06I\"\nHello\x06:\x06ETI\"\nWorld\x06;\x00T",
  deserialized: { "Hello" => "World" },
}

3.9 Active Job

3.9.1 enqueue_at.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:job Objeto del trabajo

3.9.2 enqueue.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:job Objeto del trabajo

3.9.3 enqueue_retry.active_job

Clave Valor
:job Objeto del trabajo
:adapter Objeto QueueAdapter procesando el trabajo
:error El error que causó el reintento
:wait El retraso del reintento

3.9.4 enqueue_all.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:jobs Una matriz de objetos de trabajo

3.9.5 perform_start.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:job Objeto del trabajo

3.9.6 perform.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:job Objeto del trabajo
:db_runtime Cantidad dedicada a ejecutar consultas de base de datos en ms

3.9.7 retry_stopped.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:job Objeto del trabajo
:error El error que causó el reintento

3.9.8 discard.active_job

Clave Valor
:adapter Objeto QueueAdapter procesando el trabajo
:job Objeto del trabajo
:error El error que causó el descarte

3.10 Action Cable

3.10.1 perform_action.action_cable

Clave Valor
:channel_class Nombre de la clase del canal
:action La acción
:data Un hash de datos

3.10.2 transmit.action_cable

Clave Valor
:channel_class Nombre de la clase del canal
:data Un hash de datos
:via A través de

3.10.3 transmit_subscription_confirmation.action_cable

Clave Valor
:channel_class Nombre de la clase del canal

3.10.4 transmit_subscription_rejection.action_cable

Clave Valor
:channel_class Nombre de la clase del canal

3.10.5 broadcast.action_cable

Clave Valor
:broadcasting Una transmisión nombrada
:message Un hash de mensaje
:coder El codificador

3.11 Active Storage

3.11.1 preview.active_storage

Clave Valor
:key Token seguro

3.11.2 transform.active_storage

3.11.3 analyze.active_storage

Clave Valor
:analyzer Nombre del analizador, por ejemplo, ffprobe

3.12 Active Storage: Servicio de Almacenamiento

3.12.1 service_upload.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio
:checksum Suma de verificación para asegurar la integridad

3.12.2 service_streaming_download.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio

3.12.3 service_download_chunk.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio
:range Rango de bytes que se intentó leer

3.12.4 service_download.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio

3.12.5 service_delete.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio

3.12.6 service_delete_prefixed.active_storage

Clave Valor
:prefix Prefijo de clave
:service Nombre del servicio

3.12.7 service_exist.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio
:exist Si el archivo o blob existe o no

3.12.8 service_url.active_storage

Clave Valor
:key Token seguro
:service Nombre del servicio
:url URL generada

3.12.9 service_update_metadata.active_storage

Este evento solo se emite cuando se usa el servicio de Google Cloud Storage.

Clave Valor
:key Token seguro
:service Nombre del servicio
:content_type Campo HTTP Content-Type
:disposition Campo HTTP Content-Disposition

3.13 Action Mailbox

3.13.1 process.action_mailbox

Clave Valor
:mailbox Instancia de la clase Mailbox que hereda de ActionMailbox::Base
:inbound_email Hash con datos sobre el correo entrante que se está procesando
{
  mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
  inbound_email: {
    id: 1,
    message_id: "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com",
    status: "processing"
  }
}

3.14 Railties

3.14.1 load_config_initializer.railties

Clave Valor
:initializer Ruta del inicializador cargado en config/initializers

3.15 Rails

3.15.1 deprecation.rails

Clave Valor
:message La advertencia de deprecación
:callstack De dónde proviene la deprecación
:gem_name Nombre de la gema que reporta la deprecación
:deprecation_horizon Versión donde se eliminará el comportamiento obsoleto

4 Excepciones

Si ocurre una excepción durante cualquier instrumentación, la carga útil incluirá información al respecto.

Clave Valor
:exception Una matriz de dos elementos. Nombre de la clase de excepción y el mensaje
:exception_object El objeto de excepción

5 Creación de Eventos Personalizados

Agregar tus propios eventos es fácil también. Active Support se encargará de todo el trabajo pesado por ti. Simplemente llama a ActiveSupport::Notifications.instrument con un name, payload, y un bloque. La notificación se enviará después de que el bloque regrese. Active Support generará los tiempos de inicio y fin, y agregará el ID único del instrumentador. Todos los datos pasados a la llamada instrument harán que lleguen a la carga útil.

Aquí tienes un ejemplo:

ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
  # haz tus cosas personalizadas aquí
end

Ahora puedes escuchar este evento con:

ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
  puts data.inspect # {:this=>:data}
end

También puedes llamar a instrument sin pasar un bloque. Esto te permite aprovechar la infraestructura de instrumentación para otros usos de mensajería.

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

Deberías seguir las convenciones de Rails al definir tus propios eventos. El formato es: event.library. Si tu aplicación está enviando Tweets, deberías crear un evento llamado tweet.twitter.


Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.

También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.

Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.

Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.