规范化宽事件
规范的广泛事件描述了一个已完成的工作单元以及解释其结果所需的上下文。应用程序不是根据断开连接的“开始”、“数据库调用”和“完成”消息重建请求,而是发出一个请求结果,其中包含其路由、帐户、发布、持续时间、状态和分类的故障上下文。
此模式也称为规范日志行、结构化事件或宽事件。 Stripe 将规范日志行描述为一种在一个地方收集请求的重要上下文的方法。 Honeycomb 使用上下文丰富的结构化事件作为可观测性的基础。 OpenTelemetry 日志数据模型 提供了可以将日志与跟踪关联起来的标准表示形式。名称和传输方式不同,但有用的设计问题是相同的:一条记录能否在不搜索叙述的情况下解释有意义的结果?
“广”是指活动可以承载很多有目的的领域。这并不意味着复制内存中的每个对象。
从事件粒度开始
事件粒度是由一行表示的事物。在选择字段之前将其写下来。有用的谷物包括:
- 一个 API 请求达到了最终结果
- 一项后台作业已完成或已耗尽重试次数
- 一次 Webhook 传送已被处理、拒绝或重复数据删除
- 一个代理运行已完成、失败或达到安全限制
- 一个帐户达到激活、计费或保留里程碑
- 使用有界指纹完成一次数据库操作
避免在一张桌子上混合谷物。如果一行有时意味着一次请求尝试,有时意味着所有重试中的逻辑请求,则计数和速率就会变得不明确。当需要两个视图时,请使用单独的 attempt_number 或单独的尝试事件。
在操作的生命周期中构建规范事件,并在知道最终结果时发出它:
const outcome = {
request_id: requestId,
route_template: "/api/projects/:id/sync",
method: "POST",
team_id: teamId,
release: process.env.APP_RELEASE,
environment: "production",
started_at: new Date().toISOString(),
};
try {
await syncProject();
await telemetry.log("api_request_completed", {
...outcome,
status: "success",
status_code: 200,
latency_ms: Math.round(performance.now() - startedAt),
});
} catch (error) {
await telemetry.log("api_request_completed", {
...outcome,
status: "failed",
status_code: statusFor(error),
error_type: classifyError(error),
latency_ms: Math.round(performance.now() - startedAt),
});
throw error;
}
仪器交付不应将成功的请求变成失败的请求。使用有界超时,单独观察传递失败,并明确决定哪些关键事件需要持久队列。
使用字段分类法
有用的规范事件通常来自六个字段组:
| 集团 | 示例 | 为什么存在 |
|---|---|---|
| 身份 | event_id、request_id、run_id |
删除重复数据并找到一个结果 |
| 谷物和结果 | event_name、status、error_type、attempt_number |
定义计算的内容 |
| 时机 | timestamp_utc、duration_ms、queue_wait_ms |
构建速率和延迟分布 |
| 产品背景 | feature、plan、workflow、route_template |
将可靠性与面向用户的行为联系起来 |
| 部署环境 | service、environment、region、release |
比较变化并隔离回归 |
| 相关性 | trace_id、job_id、team_id |
转向更深入的证据或加入相关活动 |
对要分组的字段使用受控类别。 error_type: "dependency_timeout" 比原始异常消息更可靠。在名称中使用明确的单位:_ms、_bytes、_usd 和 _count。使用规范化的路由模板而不是原始 URL。
请求 ID、帐户 ID 和跟踪 ID 等标识符是高基数。这通常是正确的:即使图表维度较差,它们对于过滤和关联也很有价值。仅当调查收益证明隐私、存储和查询成本合理时才保留它们。参见 高基数字段。
三种实用的活动形状
API 请求事件应将分母和结果放在一起:
{
"event_name": "api_request_completed",
"request_id": "req_7d91",
"route_template": "/api/projects/:id/sync",
"method": "POST",
"status_code": 503,
"status": "failed",
"error_type": "dependency_timeout",
"latency_ms": 8420,
"release": "2026.07.4",
"schema_version": 2
}
后台作业事件应该使重试粒度明确:
{
"event_name": "job_completed",
"job_id": "job_82f1",
"job_name": "sync_billing_account",
"queue_name": "billing",
"status": "failed",
"terminal": true,
"attempt_number": 4,
"queue_wait_ms": 1820,
"duration_ms": 9612,
"error_type": "provider_timeout"
}
代理运行事件应将操作结果与敏感内容分开:
{
"event_name": "agent_run_completed",
"run_id": "run_28bd",
"workflow": "support_resolution",
"agent_name": "support_agent",
"model": "approved_model_alias",
"status": "success",
"tool_call_count": 3,
"retry_count": 1,
"duration_ms": 4820,
"accepted": true,
"prompt_version": "support-v4"
}
默认情况下,不记录原始提示、完成、工具参数或检索的文档。结果事件可以回答数量、可靠性、成本和接受度问题,而无需保留客户内容。
缩小隐私边界
将每个字段视为可能出现在查询结果、仪表板、导出或支持工作流程中的数据。在活动构建时使用许可名单。请勿包含授权标头、cookie、凭据、连接字符串、请求或响应正文、webhook 负载、付款详细信息或不受限制的客户内容。
优先使用内部帐户标识符而非电子邮件地址,优先使用路由模板而非完整 URL,优先选择受控错误类别而非异常文本。对个人数据进行哈希处理并不会自动使其安全;稳定的哈希值仍然可以是可链接的标识符。 事件跟踪计划 中的文档所有权、目的、保留和删除期望。
关联而不是重复
规范的广泛事件补充了指标、跟踪和详细的诊断日志。它不需要复制它们。
- 指标对于聚合服务运行状况和基础设施告警仍然有效。
- 痕迹显示跨跨度的时间和因果关系。
- 诊断日志保留本地详细信息,例如堆栈跟踪。
- 规范事件保留已完成的应用程序或业务成果。
当更深入的证据存在于其他地方时,请附上经批准的 trace_id 或相关 ID。响应者可以从失败的结果行移动到其跟踪,而无需将跨度瀑布或堆栈跟踪复制到事件中。 日志、指标和跟踪指南 更详细地涵盖了边界。
在启动前规划架构演变
为该事件提供一个所有者和一个 schema_version。在将可选字段设置为必填字段之前添加它们。切勿默默地将数字字段更改为字符串或重复使用字段名称以实现不同的含义。在迁移期间,支持 SQL 中的两个架构版本,直到生产者和历史窗口收敛。
保持受控类别的界限。如果出现新的错误类别,请检查它是否更改了仪表板、告警或 Runbook。如果应用程序升级重命名路由或工作流,请与实现名称分开保留稳定的分析名称。
基于决策而不是方便的样本
不要抽取罕见故障、终端作业结果、计费更改、安全操作或用于精确协调的事件。大批量成功请求可能是确定性或基于速率采样的候选者,但如果下游分析需要估计总数,则保留采样决策和权重。
将保存的存储空间与丢失的问题进行比较。采样可以保留延迟分布,同时使精确的帐户影响计数变得不可能。 事件采样指南描述了安全和不安全的情况。
使用 SQL 验证事件
当事件以可防御的 SQL 回答其预期问题时,而不是当它包含最多字段时,事件就完成了。在非生产环境中演练成功、失败、重试、超时、重复传递、空字段和迟到路径。在构建仪表板之前检查存储的架构。
对于 API 结果事件,从计数和分母开始:
SELECT
route_template,
COUNT(*) AS requests,
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failures,
100.0 * SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0) AS failure_rate_pct,
approx_percentile_cont(latency_ms, 0.95) AS p95_latency_ms
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
AND environment = 'production'
GROUP BY route_template
HAVING COUNT(*) >= 20
ORDER BY failure_rate_pct DESC;
将数量与速率放在一起,在比较期间时排除不完整的时间段,并说明重试是尝试还是逻辑结果。为操作上变得重要的查询存储确定性装置和预期结果。 CI 指南中的仪器测试 展示了如何防止合约漂移。
一次迁移一个工作流程
不要替换整个日志流。选择一个重复决策,在现有遥测数据旁边发出其规范事件,并在同一个关闭的 UTC 窗口中双重运行新旧答案。研究重试处理、路由规范化、时间戳、空值和排除方面的差异。仅在其所有者接受语义后,才将新查询提升到仪表板或告警。
实际路径是:
- 定义事件粒度和决策。
- 编写允许的现场合同。
- 仪器终端结果。
- 验证交付和架构。
- 使用装置测试查询。
- 双运行报告或告警。
- 仅退休多余的消费者。
继续使用 结构化日志管理指南、结构化事件与文本日志 或完整的 迁移指南。