更多内容请访问 rubyonrails.org:

Active Support Instrumentation

Active Support 是 Rails 核心的一部分,提供 Ruby 语言扩展、实用程序和其他内容。其中一项功能是 Instrumentation API,它可以在应用程序内部使用,以衡量 Ruby 代码中发生的特定操作,例如 Rails 应用程序或框架本身内部的操作。但它并不局限于 Rails,可以独立地在其他 Ruby 脚本中使用。

在本指南中,您将学习如何使用 Active Support 的 Instrumentation API 来衡量 Rails 和其他 Ruby 代码内部的事件。

阅读完本指南后,您将了解

  • Instrumentation 可以提供什么。
  • 如何向 Hook 添加订阅者。
  • Rails 框架中用于 Instrumentation 的 Hook。
  • 如何构建自定义 Instrumentation 实现。

1 Instrumentation 简介

Active Support 提供的 Instrumentation API 允许开发人员提供 Hook,其他开发人员可以连接到这些 Hook。在 Rails 框架中,有 几个这样的 Hook。使用此 API,开发人员可以选择在应用程序或其他 Ruby 代码中发生某些事件时收到通知。

例如,在 Active Record 中提供了一个 Hook,它会在 Active Record 对数据库使用 SQL 查询时被调用。这个 Hook 可以被 订阅,并用于跟踪特定操作期间的查询次数。还有一个关于处理控制器操作的 Hook。例如,这可以用来跟踪特定操作所花费的时间。

您甚至可以 在您的应用程序中创建自己的事件,您以后可以订阅这些事件。

2 订阅事件

使用 ActiveSupport::Notifications.subscribe 和一个 block 来监听任何通知。根据 block 接受的参数数量,您将收到不同的数据。

订阅事件的第一种方法是使用一个带单个参数的 block。该参数将是 ActiveSupport::Notifications::Event 的一个实例。

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

  Rails.logger.info "#{event} Received!"
end

如果您不需要 Event 对象记录的所有数据,您也可以指定一个接受以下五个参数的 block

  • 事件的名称
  • 事件开始的时间
  • 事件结束的时间
  • 触发事件的 Instrumentation 的唯一 ID
  • 事件的有效载荷
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
  # your own custom stuff
  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

如果您担心 startedfinished 的准确性,无法计算出精确的经过时间,那么请使用 ActiveSupport::Notifications.monotonic_subscribe。给定的 block 将接收与上述相同的参数,但 startedfinished 将具有准确的单调时间值,而不是挂钟时间。

ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
  # your own custom stuff
  duration = finished - started # 1560979.429234 - 1560978.425334
  Rails.logger.info "#{name} Received! (duration: #{duration})" # process_action.action_controller Received! (duration: 1.0039)
end

您也可以订阅与正则表达式匹配的事件。这使您能够一次订阅多个事件。以下是订阅来自 ActionController 的所有内容的方法

ActiveSupport::Notifications.subscribe(/action_controller/) do |event|
  # inspect all ActionController events
end

3 Rails 框架 Hook

在 Ruby on Rails 框架中,提供了一些用于常见事件的 Hook。这些事件及其有效载荷在下面详细介绍。

3.1 Action Controller

3.1.1 start_processing.action_controller

:controller 控制器名称
:action 操作
:request ActionDispatch::Request 对象
: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"
}

3.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
}

3.1.3 send_file.action_controller

:path 文件的完整路径

调用方可以添加其他键。

3.1.4 send_data.action_controller

ActionController 不会向有效载荷添加任何特定信息。所有选项都将传递到有效载荷。

3.1.5 redirect_to.action_controller

:status HTTP 响应代码
:location 要重定向到的 URL
:request ActionDispatch::Request 对象
{
  status: 302,
  location: "https://127.0.0.1:3000/posts/new",
  request: <ActionDispatch::Request:0x00007ff1cb9bd7b8>
}

3.1.6 halted_callback.action_controller

:filter 阻止操作的过滤器
{
  filter: ":halting_filter"
}

3.1.7 unpermitted_parameters.action_controller

:keys 未经授权的键
:context 包含以下键的哈希::controller:action:params:request

3.1.8 send_stream.action_controller

:filename 文件名
:type HTTP 内容类型
:disposition HTTP 内容处置
{
  filename: "subscribers.csv",
  type: "text/csv",
  disposition: "attachment"
}

3.2 Action Controller:缓存

3.2.1 write_fragment.action_controller

:key 完整键
{
  key: 'posts/1-dashboard-view'
}

3.2.2 read_fragment.action_controller

:key 完整键
{
  key: 'posts/1-dashboard-view'
}

3.2.3 expire_fragment.action_controller

:key 完整键
{
  key: 'posts/1-dashboard-view'
}

3.2.4 exist_fragment?.action_controller

:key 完整键
{
  key: 'posts/1-dashboard-view'
}

3.3 Action Dispatch

3.3.1 process_middleware.action_dispatch

:middleware 中间件的名称

3.3.2 redirect.action_dispatch

:status HTTP 响应代码
:location 要重定向到的 URL
:request ActionDispatch::Request 对象

3.3.3 request.action_dispatch

:request ActionDispatch::Request 对象

3.4 Action View

3.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" }
}

3.4.2 render_partial.action_view

:identifier 模板的完整路径
:locals 传递给模板的局部变量
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
  locals: { foo: "bar" }
}

3.4.3 render_collection.action_view

:identifier 模板的完整路径
:count 集合的大小
:cache_hits 从缓存中获取的局部模板数量

仅当使用 cached: true 渲染集合时,才会包含 :cache_hits 键。

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

3.4.4 render_layout.action_view

:identifier 模板的完整路径
{
  identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
}

3.5 Active Record

3.5.1 sql.active_record

:sql SQL 语句
:name 操作的名称
:connection 连接对象
:transaction 当前事务(如果有)
:binds 绑定参数
:type_casted_binds 类型转换后的绑定参数
:statement_name SQL 语句名称
:async true 表示查询是异步加载的
:cached true 表示使用缓存查询时添加的
:row_count 查询返回的行数

适配器也可以添加它们自己的数据。

{
  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
}

如果查询不是在事务的上下文中执行的,则 :transactionnil

3.5.2 strict_loading_violation.active_record

仅当 config.active_record.action_on_strict_loading_violation 设置为 :log 时,才会发出此事件。

:owner 启用了 strict_loading 的模型
:reflection 尝试加载的关联的反射

3.5.3 instantiation.active_record

:record_count 实例化的记录数量
:class_name 记录的类
{
  record_count: 1,
  class_name: "User"
}

3.5.4 start_transaction.active_record

当事务开始时,会发出此事件。

:transaction 事务对象
:connection 连接对象

请注意,Active Record 不会在需要之前创建实际的数据库事务

ActiveRecord::Base.transaction do
  # We are inside the block, but no event has been triggered yet.

  # The following line makes Active Record start the transaction.
  User.count # Event fired here.
end

请记住,普通的嵌套调用不会创建新的事务

ActiveRecord::Base.transaction do |t1|
  User.count # Fires an event for t1.
  ActiveRecord::Base.transaction do |t2|
    # The next line fires no event for t2, because the only
    # real database transaction in this example is t1.
    User.first.touch
  end
end

但是,如果传递 requires_new: true,您也会收到嵌套事务的事件。这在幕后可能是保存点

ActiveRecord::Base.transaction do |t1|
  User.count # Fires an event for t1.
  ActiveRecord::Base.transaction(requires_new: true) do |t2|
    User.first.touch # Fires an event for t2.
  end
end

3.5.5 transaction.active_record

当数据库事务结束时,会发出此事件。事务的状态可以在 :outcome 键中找到。

:transaction 事务对象
:outcome :commit:rollback:restart:incomplete
:connection 连接对象

实际上,您无法对事务对象进行太多操作,但它可能仍然有助于跟踪数据库活动。例如,通过跟踪transaction.uuid

3.6 Action Mailer

3.6.1 deliver.action_mailer

:mailer 邮件器类的名称
:message_id 邮件 ID,由 Mail gem 生成
:subject 邮件的主题
:to 邮件的收件人地址
:from 邮件的发送人地址
:bcc 邮件的密件抄送地址
:cc 邮件的抄送地址
:date 邮件的日期
:mail 邮件的编码形式
:perform_deliveries 是否执行此邮件的传递
{
  mailer: "Notification",
  message_id: "[email protected]",
  subject: "Rails Guides",
  to: ["[email protected]", "[email protected]"],
  from: ["[email protected]"],
  date: Sat, 10 Mar 2012 14:18:09 +0100,
  mail: "...", # omitted for brevity
  perform_deliveries: true
}

3.6.2 process.action_mailer

:mailer 邮件器类的名称
:action 操作
:args 参数
{
  mailer: "Notification",
  action: "welcome_email",
  args: []
}

3.7 Active Support: 缓存

3.7.1 cache_read.active_support

:key 存储中使用的键
:store 存储类的名称
:hit 如果此读取命中
:super_operation :fetch 如果读取使用 fetch 完成

3.7.2 cache_read_multi.active_support

:key 存储中使用的键
:store 存储类的名称
:hits 缓存命中的键
:super_operation :fetch_multi 如果读取使用 fetch_multi 完成

3.7.3 cache_generate.active_support

此事件仅在使用 fetch 时调用块时发出。

:key 存储中使用的键
:store 存储类的名称

传递给fetch 的选项将在写入存储时与有效负载合并。

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

3.7.4 cache_fetch_hit.active_support

此事件仅在使用 fetch 时调用块时发出。

:key 存储中使用的键
:store 存储类的名称

传递给fetch 的选项将与有效负载合并。

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

3.7.5 cache_write.active_support

:key 存储中使用的键
:store 存储类的名称

缓存存储也可能添加自己的数据。

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

3.7.6 cache_write_multi.active_support

:key 写入存储的键和值
:store 存储类的名称

3.7.7 cache_increment.active_support

:key 存储中使用的键
:store 存储类的名称
:amount 增量数量
{
  key: "bottles-of-beer",
  store: "ActiveSupport::Cache::RedisCacheStore",
  amount: 99
}

3.7.8 cache_decrement.active_support

:key 存储中使用的键
:store 存储类的名称
:amount 递减数量
{
  key: "bottles-of-beer",
  store: "ActiveSupport::Cache::RedisCacheStore",
  amount: 1
}

3.7.9 cache_delete.active_support

:key 存储中使用的键
:store 存储类的名称
{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.7.10 cache_delete_multi.active_support

:key 存储中使用的键
:store 存储类的名称

3.7.11 cache_delete_matched.active_support

此事件仅在使用 RedisCacheStoreFileStoreMemoryStore 时发出。

:key 使用的键模式
:store 存储类的名称
{
  key: "posts/*",
  store: "ActiveSupport::Cache::RedisCacheStore"
}

3.7.12 cache_cleanup.active_support

此事件仅在使用 MemoryStore 时发出。

:store 存储类的名称
:size 清理前缓存中的条目数
{
  store: "ActiveSupport::Cache::MemoryStore",
  size: 9001
}

3.7.13 cache_prune.active_support

此事件仅在使用 MemoryStore 时发出。

:store 存储类的名称
:key 缓存的目标大小(以字节为单位)
:from 修剪前缓存的大小(以字节为单位)
{
  store: "ActiveSupport::Cache::MemoryStore",
  key: 5000,
  from: 9001
}

3.7.14 cache_exist?.active_support

:key 存储中使用的键
:store 存储类的名称
{
  key: "name-of-complicated-computation",
  store: "ActiveSupport::Cache::MemCacheStore"
}

3.8 Active Support: 消息

3.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" },
}

3.9 Active Job

3.9.1 enqueue_at.active_job

:adapter 处理作业的 QueueAdapter 对象
:job 作业对象

3.9.2 enqueue.active_job

:adapter 处理作业的 QueueAdapter 对象
:job 作业对象

3.9.3 enqueue_retry.active_job

:job 作业对象
:adapter 处理作业的 QueueAdapter 对象
:error 导致重试的错误
:wait 重试的延迟

3.9.4 enqueue_all.active_job

:adapter 处理作业的 QueueAdapter 对象
:jobs 作业对象的数组

3.9.5 perform_start.active_job

:adapter 处理作业的 QueueAdapter 对象
:job 作业对象

3.9.6 perform.active_job

:adapter 处理作业的 QueueAdapter 对象
:job 作业对象
:db_runtime 执行数据库查询所花费的时间(毫秒)

3.9.7 retry_stopped.active_job

:adapter 处理作业的 QueueAdapter 对象
:job 作业对象
:error 导致重试的错误

3.9.8 discard.active_job

:adapter 处理作业的 QueueAdapter 对象
:job 作业对象
:error 导致丢弃的错误

3.10 Action Cable

3.10.1 perform_action.action_cable

:channel_class 通道类的名称
:action 操作
:data 数据的哈希表

3.10.2 transmit.action_cable

:channel_class 通道类的名称
:data 数据的哈希表
:via 通过

3.10.3 transmit_subscription_confirmation.action_cable

:channel_class 通道类的名称

3.10.4 transmit_subscription_rejection.action_cable

:channel_class 通道类的名称

3.10.5 broadcast.action_cable

:broadcasting 命名广播
:message 消息的哈希表
:coder 编码器

3.11 Active Storage

3.11.1 preview.active_storage

:key 安全令牌

3.11.2 transform.active_storage

3.11.3 analyze.active_storage

:analyzer 分析器的名称,例如,ffprobe

3.12 Active Storage: 存储服务

3.12.1 service_upload.active_storage

:key 安全令牌
:service 服务的名称
:checksum 校验和,以确保完整性

3.12.2 service_streaming_download.active_storage

:key 安全令牌
:service 服务的名称

3.12.3 service_download_chunk.active_storage

:key 安全令牌
:service 服务的名称
:range 尝试读取的字节范围

3.12.4 service_download.active_storage

:key 安全令牌
:service 服务的名称

3.12.5 service_delete.active_storage

:key 安全令牌
:service 服务的名称

3.12.6 service_delete_prefixed.active_storage

:prefix 键前缀
:service 服务的名称

3.12.7 service_exist.active_storage

:key 安全令牌
:service 服务的名称
:exist 文件或 Blob 是否存在

3.12.8 service_url.active_storage

:key 安全令牌
:service 服务的名称
:url 生成的 URL

3.12.9 service_update_metadata.active_storage

此事件仅在使用 Google Cloud Storage 服务时发出。

:key 安全令牌
:service 服务的名称
:content_type HTTP Content-Type 字段
:disposition HTTP Content-Disposition 字段

3.13 Action Mailbox

3.13.1 process.action_mailbox

:mailbox 继承自 ActionMailbox::Base 的 Mailbox 类的实例
:inbound_email 包含有关正在处理的入站电子邮件的数据的哈希表
{
  mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
  inbound_email: {
    id: 1,
    message_id: "[email protected]",
    status: "processing"
  }
}

3.14 Railties

3.14.1 load_config_initializer.railties

:initializer config/initializers 中加载的初始化程序的路径

3.15 Rails

3.15.1 deprecation.rails

:message 弃用警告
:callstack 弃用来自何处
:gem_name 报告弃用的 gem 的名称
:deprecation_horizon 将删除弃用行为的版本

4 异常

如果在任何检测期间发生异常,有效负载将包含有关该异常的信息。

:exception 包含两个元素的数组。异常类名和消息
:exception_object 异常对象

5 创建自定义事件

添加您自己的事件也很容易。Active Support 将为您处理所有繁重的工作。只需使用namepayload 和一个块调用 ActiveSupport::Notifications.instrument。块返回后将发送通知。Active Support 将生成开始和结束时间,并添加检测器的唯一 ID。传递给instrument 调用的所有数据都将进入有效负载。

以下是一个示例

ActiveSupport::Notifications.instrument "my.custom.event", this: :data do
  # do your custom stuff here
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。如果您的应用程序正在发送推文,您应该创建一个名为tweet.twitter 的事件。



返回顶部