Active Support 提供的检测 API 允许开发人员提供钩子,其他开发人员可以挂接这些钩子。Rails 框架中有几个这样的钩子 。使用此 API,开发人员可以选择在应用程序或另一段 Ruby 代码中发生特定事件时收到通知。
例如,Active Record 中提供了一个钩子 ,每当 Active Record 在数据库上使用 SQL 查询时都会调用该钩子。可以订阅 此钩子,并用于跟踪特定操作期间的查询数量。控制器操作的处理周围还有另一个钩子 。例如,这可以用于跟踪特定操作花费了多长时间。
你甚至可以在应用程序中创建自己的事件 ,稍后可以订阅这些事件。
使用带块的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
复制
如果你关心 started 和 finished 的准确性以计算精确的经过时间,那么请使用 ActiveSupport::Notifications.monotonic_subscribe 。给定的块将接收与上面相同的参数,但 started 和 finished 将具有精确的单调时间值,而不是挂钟时间。
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
复制
在 Ruby on Rails 框架中,为常见事件提供了许多钩子。这些事件及其有效负载详细如下。
键
值
:channel_class
通道类名
:action
操作
:data
数据哈希
键
值
:channel_class
通道类名
:data
数据哈希
:via
通过
键
值
:broadcasting
命名广播
:message
消息哈希
:coder
编码器
键
值
: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"
}
复制
键
值
: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
}
复制
调用者可以添加额外的键。
ActionController 不会向有效负载添加任何特定信息。所有选项都传递给有效负载。
{
status: 302 ,
location: "https://:3000/posts/new" ,
request: < ActionDispatch :: Request : 0x00007ff1cb9bd7b8 >
}
复制
{
filter: ":halting_filter"
}
复制
键
值
:keys
未允许的键
:context
包含以下键的哈希::controller、:action、:params、:request
键
值
:filename
文件名
:type
HTTP 内容类型
:disposition
HTTP 内容处置
{
filename: "subscribers.csv" ,
type: "text/csv" ,
disposition: "attachment"
}
复制
键
值
:request
ActionDispatch::Request 对象
:count
发出的请求数量
:to
允许的最大请求数量
:within
速率限制的时间窗口
:by
速率限制的标识符(例如 IP)
:name
速率限制的名称
:scope
速率限制的范围
:cache_key
用于存储速率限制的缓存键
{
key: 'posts/1-dashboard-view'
}
复制
{
key: 'posts/1-dashboard-view'
}
复制
{
key: 'posts/1-dashboard-view'
}
复制
{
key: 'posts/1-dashboard-view'
}
复制
{
mailbox: #<RepliesMailbox:0x00007f9f7a8388>,
inbound_email: {
id: 1 ,
message_id: "0CB459E0-0336-41DA-BC88-E6E28C697DDB@37signals.com" ,
status: "processing"
}
}
复制
键
值
: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
}
复制
键
值
:mailer
邮件器类名
:action
操作
:args
参数
{
mailer: "Notification" ,
action: "welcome_email" ,
args: []
}
复制
键
值
:identifier
模板的完整路径
:layout
适用的布局
:locals
传递给模板的局部变量
{
identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb" ,
layout: "layouts/application" ,
locals: { foo: "bar" }
}
复制
键
值
:identifier
模板的完整路径
:locals
传递给模板的局部变量
{
identifier: "/Users/adam/projects/notifications/app/views/posts/_form.html.erb" ,
locals: { foo: "bar" }
}
复制
键
值
:identifier
模板的完整路径
:count
集合大小
:cache_hits
从缓存中获取的部分数量
只有当集合以 cached: true 渲染时,才包含 :cache_hits 键。
{
identifier: "/Users/adam/projects/notifications/app/views/posts/_post.html.erb" ,
count: 3 ,
cache_hits: 0
}
复制
{
identifier: "/Users/adam/projects/notifications/app/views/layouts/application.html.erb"
}
复制
键
值
:adapter
处理作业的 QueueAdapter 对象
:job
Job 对象
键
值
:adapter
处理作业的 QueueAdapter 对象
:job
Job 对象
键
值
:job
Job 对象
:adapter
处理作业的 QueueAdapter 对象
:error
导致重试的错误
:wait
重试的延迟
键
值
:adapter
处理作业的 QueueAdapter 对象
:jobs
Job 对象数组
键
值
:adapter
处理作业的 QueueAdapter 对象
:job
Job 对象
键
值
:adapter
处理作业的 QueueAdapter 对象
:job
Job 对象
:db_runtime
执行数据库查询花费的时间(毫秒)
键
值
:adapter
处理作业的 QueueAdapter 对象
:job
Job 对象
:error
导致重试的错误
键
值
:adapter
处理作业的 QueueAdapter 对象
:job
Job 对象
:error
导致丢弃的错误
键
值
: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 ,
}
复制
如果查询未在事务上下文中执行,则 :transaction 为 nil。
仅当 config.active_record.action_on_strict_loading_violation 设置为 :log 时才发出此事件。
键
值
:owner
启用 strict_loading 的模型
:reflection
尝试加载的关联的反射
键
值
:record_count
实例化的记录数
:class_name
记录的类
{
record_count: 1 ,
class_name: "User"
}
复制
此事件在事务开始时发出。
键
值
: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
复制
此事件在数据库事务结束时发出。事务的状态可以在 :outcome 键中找到。
键
值
:transaction
事务对象
:outcome
:commit、:rollback、:restart 或 :incomplete
:connection
连接对象
实际上,你无法对事务对象做太多事情,但它仍然有助于跟踪数据库活动。例如,通过跟踪 transaction.uuid。
当访问已弃用的关联且配置的已弃用关联模式为 :notify 时,会发出此事件。
键
值
:reflection
关联的反射
:message
有关访问的描述性消息
:location
访问的应用程序级别位置
:backtrace
仅当选项 :backtrace 为 true 时存在
:location 是一个 Thread::Backtrace::Location 对象,而 :backtrace(如果存在)是 Thread::Backtrace::Location 对象的数组。这些是使用 Active Record 回溯清理器计算的。在 Rails 应用程序中,这与 Rails.backtrace_cleaner 相同。
键
值
:analyzer
分析器名称,例如 ffprobe
键
值
:key
安全令牌
:service
服务名称
:checksum
校验和以确保完整性
键
值
:key
安全令牌
:service
服务名称
键
值
:key
安全令牌
:service
服务名称
:range
尝试读取的字节范围
键
值
:key
安全令牌
:service
服务名称
键
值
:key
安全令牌
:service
服务名称
键
值
:prefix
键前缀
:service
服务名称
键
值
:key
安全令牌
:service
服务名称
:exist
文件或 Blob 是否存在
键
值
:key
安全令牌
:service
服务名称
:url
生成的 URL
此事件仅在使用 Google Cloud Storage 服务时发出。
键
值
:key
安全令牌
:service
服务名称
:content_type
HTTP Content-Type 字段
:disposition
HTTP Content-Disposition 字段
键
值
:key
存储中使用的键
:store
存储类名称
:hit
如果此读取是命中
:super_operation
如果使用 fetch 完成读取,则为 :fetch
键
值
:key
存储中使用的键
:store
存储类名称
:hits
缓存命中的键
:super_operation
如果使用 fetch_multi 完成读取,则为 :fetch_multi
此事件仅当调用 fetch 时带有块时才发出。
键
值
:key
存储中使用的键
:store
存储类名称
传递给 fetch 的选项将在写入存储时与有效负载合并。
{
key: "name-of-complicated-computation" ,
store: "ActiveSupport::Cache::MemCacheStore"
}
复制
此事件仅当调用 fetch 时带有块时才发出。
键
值
:key
存储中使用的键
:store
存储类名称
传递给 fetch 的选项将与有效负载合并。
{
key: "name-of-complicated-computation" ,
store: "ActiveSupport::Cache::MemCacheStore"
}
复制
键
值
:key
存储中使用的键
:store
存储类名称
缓存存储也可以添加自己的数据。
{
key: "name-of-complicated-computation" ,
store: "ActiveSupport::Cache::MemCacheStore"
}
复制
键
值
:key
写入存储的键和值
:store
存储类名称
键
值
:key
存储中使用的键
:store
存储类名称
:amount
增量金额
{
key: "bottles-of-beer" ,
store: "ActiveSupport::Cache::RedisCacheStore" ,
amount: 99
}
复制
键
值
:key
存储中使用的键
:store
存储类名称
:amount
减量金额
{
key: "bottles-of-beer" ,
store: "ActiveSupport::Cache::RedisCacheStore" ,
amount: 1
}
复制
键
值
:key
存储中使用的键
:store
存储类名称
{
key: "name-of-complicated-computation" ,
store: "ActiveSupport::Cache::MemCacheStore"
}
复制
键
值
:key
存储中使用的键
:store
存储类名称
此事件仅在使用 RedisCacheStore 、FileStore 或 MemoryStore 时发出。
键
值
:key
使用的键模式
:store
存储类名称
{
key: "posts/*" ,
store: "ActiveSupport::Cache::RedisCacheStore"
}
复制
此事件仅在使用 MemoryStore 时发出。
键
值
:store
存储类名称
:size
清理前缓存中的条目数
{
store: "ActiveSupport::Cache::MemoryStore" ,
size: 9001
}
复制
此事件仅在使用 MemoryStore 时发出。
键
值
:store
存储类名称
:key
缓存的目标大小(字节)
:from
修剪前缓存的大小(字节)
{
store: "ActiveSupport::Cache::MemoryStore" ,
key: 5000 ,
from: 9001
}
复制
键
值
:key
存储中使用的键
:store
存储类名称
{
key: "name-of-complicated-computation" ,
store: "ActiveSupport::Cache::MemCacheStore"
}
复制
键
值
:serializer
主(预期)序列化器
:fallback
回退(实际)序列化器
:serialized
序列化字符串
:deserialized
反序列化值
{
serializer: :json_allow_marshal ,
fallback: :marshal ,
serialized: " \x04\b { \x06 I \"\n Hello \x06 : \x06 ETI \"\n World \x06 ; \x00 T" ,
deserialized: { "Hello" => "World" },
}
复制
键
值
:message
弃用警告
:callstack
弃用来源
:gem_name
报告弃用的 gem 名称
:deprecation_horizon
弃用行为将被移除的版本
键
值
:initializer
config/initializers 中加载的初始化器的路径
如果在任何检测期间发生异常,有效负载将包含有关它的信息。
键
值
:exception
一个包含两个元素的数组。异常类名和消息
:exception_object
异常对象
添加自己的事件也很容易。Active Support 将为你完成所有繁重的工作。只需调用带 name、payload 和块的 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 的事件。