Active Support 是 Rails 核心的一部分,提供 Ruby 语言扩展、实用方法等。其中包括一份监测 API,在应用中可以用它测度 Ruby 代码(如 Rails 应用或框架自身)中的特定操作。不过,这个 API 不限于只能在 Rails 中使用,如果愿意,也可以在其他 Ruby 脚本中使用。

本文教你如何使用 Active Support 中的监测 API 测度 Rails 和其他 Ruby 代码中的事件。

读完本文后,您将学到:

  • 使用监测程序能做什么;
  • Rails 框架为监测提供的钩子;
  • 订阅钩子;
  • 自定义监测点。

NOTE: 本文原文尚未完工!

监测程序简介

Active Support 提供的监测 API 允许开发者提供钩子,供其他开发者订阅。在 Rails 框架中,有很多。通过这个 API,开发者可以选择在应用或其他 Ruby 代码中发生特定事件时接收通知。

例如,Active Record 中有一个钩子,在每次使用 SQL 查询数据库时调用。开发者可以订阅这个钩子,记录特定操作执行的查询次数。还有一个钩子在控制器的动作执行前后调用,记录动作的执行时间。

在应用中甚至还可以自己创建事件,然后订阅。

Rails 框架中的钩子

Ruby on Rails 框架为很多常见的事件提供了钩子。下面详述。

Action Controller

write_fragment.action_controller

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

read_fragment.action_controller

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

expire_fragment.action_controller

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

exist_fragment?.action_controller

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

write_page.action_controller

:path 完整的路径
{
  path: '/users/1'
}

expire_page.action_controller

:path 完整的路径
{
  path: '/users/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"
}

process_action.action_controller

:controller 控制器名
:action 动作名
:params 请求参数散列,不过滤
:headers 请求首部
:format html、js、json、xml 等
:method HTTP 请求方法
:path 请求路径
:status HTTP 状态码
:view_runtime 花在视图上的时间量(ms)
:db_runtime 执行数据库查询的时间量(ms)
{
  controller: "PostsController",
  action: "index",
  params: {"action" => "index", "controller" => "posts"},
  headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
  format: :html,
  method: "GET",
  path: "/posts",
  status: 200,
  view_runtime: 46.848,
  db_runtime: 0.157
}

send_file.action_controller

:path 文件的完整路径

TIP: 调用方可以添加额外的键。

send_data.action_controller

ActionController 在载荷(payload)中没有任何特定的信息。所有选项都传到载荷中。

redirect_to.action_controller

:status HTTP 响应码
:location 重定向的 URL
{
  status: 302,
  location: "http://localhost:3000/posts/new"
}

halted_callback.action_controller

:filter 过滤暂停的动作
{
  filter: ":halting_filter"
}

Action View

render_template.action_view

:identifier 模板的完整路径
:layout 使用的布局
{
  identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
  layout: "layouts/application"
}

render-partial-action-view

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

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
}

Active Record

sql.active_record

:sql SQL 语句
:name 操作的名称
:connection_id self.object_id
:binds 绑定的参数
:cached 使用缓存的查询时为 true

TIP: 适配器也会添加数据。

{
  sql: "SELECT \"posts\".* FROM \"posts\" ",
  name: "Post Load",
  connection_id: 70307250813140,
  binds: []
}

instantiation.active_record

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

Action Mailer

receive.action_mailer

:mailer 邮件程序类的名称
:message_id 邮件的 ID,由 Mail gem 生成
:subject 邮件的主题
:to 邮件的收件地址
:from 邮件的发件地址
:bcc 邮件的密送地址
:cc 邮件的抄送地址
:date 发送邮件的日期
:mail 邮件的编码形式
{
  mailer: "Notification",
  message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
  subject: "Rails Guides",
  to: ["users@rails.com", "ddh@rails.com"],
  from: ["me@rails.com"],
  date: Sat, 10 Mar 2012 14:18:09 +0100,
  mail: "..." # 为了节省空间,省略
}

deliver.action_mailer

:mailer 邮件程序类的名称
:message_id 邮件的 ID,由 Mail gem 生成
:subject 邮件的主题
:to 邮件的收件地址
:from 邮件的发件地址
:bcc 邮件的密送地址
:cc 邮件的抄送地址
:date 发送邮件的日期
:mail 邮件的编码形式
{
  mailer: "Notification",
  message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
  subject: "Rails Guides",
  to: ["users@rails.com", "ddh@rails.com"],
  from: ["me@rails.com"],
  date: Sat, 10 Mar 2012 14:18:09 +0100,
  mail: "..." # 为了节省空间,省略
}

Active Support

cache_read.active_support

:key 存储器中使用的键
:hit 是否读取了缓存
:super_operation 如果使用 #fetch 读取了,添加 :fetch

cache_generate.active_support

仅当使用块调用 #fetch 时使用这个事件。

:key 存储器中使用的键

TIP: 写入存储器时,传给 fetch 的选项会合并到载荷中。

{
  key: 'name-of-complicated-computation'
}

cache_fetch_hit.active_support

仅当使用块调用 #fetch 时使用这个事件。

:key 存储器中使用的键

TIP: 传给 fetch 的选项会合并到载荷中。

{
  key: 'name-of-complicated-computation'
}

cache_write.active_support

:key 存储器中使用的键

TIP: 缓存存储器可能会添加其他键。

{
  key: 'name-of-complicated-computation'
}

cache_delete.active_support

:key 存储器中使用的键
{
  key: 'name-of-complicated-computation'
}

cache_exist?.active_support

:key 存储器中使用的键
{
  key: 'name-of-complicated-computation'
}

Active Job

enqueue_at.active_job

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

enqueue.active_job

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

perform_start.active_job

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

perform.active_job

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

Railties

load_config_initializer.railties

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

Rails

deprecation.rails

:message 弃用提醒
:callstack 弃用的位置

订阅事件

订阅事件是件简单的事,在 ActiveSupport::Notifications.subscribe 的块中监听通知即可。

这个块接收下述参数:

  • 事件的名称
  • 开始时间
  • 结束时间
  • 事件的唯一 ID
  • 载荷(参见前述各节)
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
  # 自己编写的其他代码
  Rails.logger.info "#{name} Received!"
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

多数时候,我们只关注数据本身。下面是只获取数据的简洁方式:

ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
  data = args.extract_options!
  data # { extra: :information }
end

此外,还可以订阅匹配正则表达式的事件。这样可以一次订阅多个事件。下面是订阅 ActionController 中所有事件的方式:

ActiveSupport::Notifications.subscribe /action_controller/ do |*args|
  # 审查所有 ActionController 事件
end

自定义事件

自己添加事件也很简单,繁重的工作都由 ActiveSupport::Notifications 代劳,我们只需调用 instrument,并传入 namepayload 和一个块。通知在块返回后发送。ActiveSupport 会生成起始时间和唯一的 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

自己定义事件时,应该遵守 Rails 的约定。事件名称的格式是 event.library。如果应用发送推文,应该把事件命名为 tweet.twitter