更多内容请访问 rubyonrails.org:

Active Support 检测

Active Support 是 Rails 核心的一部分,它提供 Ruby 语言扩展、实用工具和其他功能。它其中一项功能是检测 API,可以在应用程序内部用于测量 Ruby 代码中发生的某些操作,例如 Rails 应用程序或框架本身中的操作。但它不限于 Rails。如果需要,它可以在其他 Ruby 脚本中独立使用。

在本指南中,你将学习如何使用 Active Support 的检测 API 来测量 Rails 和其他 Ruby 代码中的事件。

阅读本指南后,您将了解

  • 检测可以提供什么。
  • 如何向钩子添加订阅者。
  • Rails 框架内部用于检测的钩子。
  • 如何构建自定义检测实现。

1. 检测简介

Active Support 提供的检测 API 允许开发人员提供钩子,其他开发人员可以挂接这些钩子。Rails 框架中有几个这样的钩子。使用此 API,开发人员可以选择在应用程序或另一段 Ruby 代码中发生特定事件时收到通知。

例如,Active Record 中提供了一个钩子,每当 Active Record 在数据库上使用 SQL 查询时都会调用该钩子。可以订阅此钩子,并用于跟踪特定操作期间的查询数量。控制器操作的处理周围还有另一个钩子。例如,这可以用于跟踪特定操作花费了多长时间。

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

2. 订阅事件

使用带块的ActiveSupport::Notifications.subscribe 来监听任何通知。根据块接受的参数数量,你将收到不同的数据。

订阅事件的第一种方法是使用带单个参数的块。该参数将是 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 对象记录的所有数据,你还可以指定一个接受以下五个参数的块:

  • 事件名称
  • 事件开始时间
  • 事件结束时间
  • 触发事件的检测器的唯一 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。给定的块将接收与上面相同的参数,但 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 框架钩子

在 Ruby on Rails 框架中,为常见事件提供了许多钩子。这些事件及其有效负载详细如下。

3.1. Action Cable

3.1.1. perform_action.action_cable

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

3.1.2. transmit.action_cable

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

3.1.3. transmit_subscription_confirmation.action_cable

:channel_class 通道类名

3.1.4. transmit_subscription_rejection.action_cable

:channel_class 通道类名

3.1.5. broadcast.action_cable

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

3.2. Action Controller

3.2.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.2.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.2.3. send_file.action_controller

:path 文件的完整路径

调用者可以添加额外的键。

3.2.4. send_data.action_controller

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

3.2.5. redirect_to.action_controller

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

3.2.6. halted_callback.action_controller

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

3.2.7. unpermitted_parameters.action_controller

:keys 未允许的键
:context 包含以下键的哈希::controller:action:params:request

3.2.8. send_stream.action_controller

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

3.2.9. rate_limit.action_controller

:request ActionDispatch::Request 对象
:count 发出的请求数量
:to 允许的最大请求数量
:within 速率限制的时间窗口
:by 速率限制的标识符(例如 IP)
:name 速率限制的名称
:scope 速率限制的范围
:cache_key 用于存储速率限制的缓存键

3.3. Action Controller:缓存

3.3.1. write_fragment.action_controller

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

3.3.2. read_fragment.action_controller

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

3.3.3. expire_fragment.action_controller

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

3.3.4. exist_fragment?.action_controller

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

3.4. Action Dispatch

3.4.1. process_middleware.action_dispatch

:middleware 中间件名称

3.4.2. redirect.action_dispatch

:status HTTP 响应码
:location 重定向到的 URL
:request ActionDispatch::Request 对象
:source_location 路由中重定向的源位置

3.4.3. request.action_dispatch

:request ActionDispatch::Request 对象

3.5. Action Mailbox

3.5.1. process.action_mailbox

:mailbox 继承自 ActionMailbox::Base 的 Mailbox 类实例
:inbound_email 包含正在处理的入站电子邮件数据的哈希
{
  mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
  inbound_email: {
    id: 1,
    message_id: "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com",
    status: "processing"
  }
}

3.6. Action Mailer

3.6.1. deliver.action_mailer

:mailer 邮件器类名
:message_id 由 Mail gem 生成的消息 ID
:subject 邮件主题
:to 邮件的收件人地址
:from 邮件发件人地址
:bcc 邮件密送地址
: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: "...", # omitted for brevity
  perform_deliveries: true
}

3.6.2. process.action_mailer

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

3.7. Action View

3.7.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.7.2. render_partial.action_view

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

3.7.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.7.4. render_layout.action_view

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

3.8. Active Job

3.8.1. enqueue_at.active_job

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

3.8.2. enqueue.active_job

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

3.8.3. enqueue_retry.active_job

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

3.8.4. enqueue_all.active_job

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

3.8.5. perform_start.active_job

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

3.8.6. perform.active_job

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

3.8.7. retry_stopped.active_job

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

3.8.8. discard.active_job

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

3.9. Active Record

3.9.1. sql.active_record

:sql SQL 语句
:name 操作名称
:binds 绑定参数
:type_casted_binds 类型转换后的绑定参数
:async 如果查询异步加载,则为 true
:allow_retry 如果查询可以自动重试,则为 true
:connection 连接对象
:transaction 当前事务(如果有)
:affected_rows 受查询影响的行数
:row_count 查询返回的行数
:cached 当结果来自查询缓存时添加 true
:statement_name SQL 语句名称(仅限 Postgres)

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

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

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

3.9.2. strict_loading_violation.active_record

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

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

3.9.3. instantiation.active_record

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

3.9.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.9.5. transaction.active_record

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

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

实际上,你无法对事务对象做太多事情,但它仍然有助于跟踪数据库活动。例如,通过跟踪 transaction.uuid

3.9.6. deprecated_association.active_record

当访问已弃用的关联且配置的已弃用关联模式为 :notify 时,会发出此事件。

:reflection 关联的反射
:message 有关访问的描述性消息
:location 访问的应用程序级别位置
:backtrace 仅当选项 :backtrace 为 true 时存在

:location 是一个 Thread::Backtrace::Location 对象,而 :backtrace(如果存在)是 Thread::Backtrace::Location 对象的数组。这些是使用 Active Record 回溯清理器计算的。在 Rails 应用程序中,这与 Rails.backtrace_cleaner 相同。

3.10. Active Storage

3.10.1. preview.active_storage

:key 安全令牌

3.10.2. transform.active_storage

3.10.3. analyze.active_storage

:analyzer 分析器名称,例如 ffprobe

3.11. Active Storage:存储服务

3.11.1. service_upload.active_storage

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

3.11.2. service_streaming_download.active_storage

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

3.11.3. service_download_chunk.active_storage

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

3.11.4. service_download.active_storage

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

3.11.5. service_delete.active_storage

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

3.11.6. service_delete_prefixed.active_storage

:prefix 键前缀
:service 服务名称

3.11.7. service_exist.active_storage

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

3.11.8. service_url.active_storage

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

3.11.9. service_update_metadata.active_storage

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

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

3.12. Active Support:缓存

3.12.1. cache_read.active_support

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

3.12.2. cache_read_multi.active_support

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

3.12.3. cache_generate.active_support

此事件仅当调用 fetch 时带有块时才发出。

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

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

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

3.12.4. cache_fetch_hit.active_support

此事件仅当调用 fetch 时带有块时才发出。

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

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

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

3.12.5. cache_write.active_support

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

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

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

3.12.6. cache_write_multi.active_support

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

3.12.7. cache_increment.active_support

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

3.12.8. cache_decrement.active_support

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

3.12.9. cache_delete.active_support

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

3.12.10. cache_delete_multi.active_support

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

3.12.11. cache_delete_matched.active_support

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

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

3.12.12. cache_cleanup.active_support

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

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

3.12.13. cache_prune.active_support

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

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

3.12.14. cache_exist?.active_support

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

3.13. Active Support:消息

3.13.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.14. Rails

3.14.1. deprecation.rails

:message 弃用警告
:callstack 弃用来源
:gem_name 报告弃用的 gem 名称
:deprecation_horizon 弃用行为将被移除的版本

3.15. Railties

3.15.1. load_config_initializer.railties

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

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 的事件。



回到顶部