从 Prometheus 告警到事件证据:把指标和日志真正接起来

用低基数指标发现异常,用结构化完成日志保留事件上下文,再将告警标签和时间范围接到可编辑查询,让调查从事件分布走向具体 Trace

作者 秦晓辉

假设一条告警告诉你:过去 5 分钟,某区域的 webhook 投递失败率从平时的 1% 升到了 8%。这是一个示意场景,但接下来的问题很具体:哪些目标失败了?涉及哪些账号?增加的是连接超时,还是鉴权错误?

Prometheus 已经发现了异常,调查却常常还要重新开始。打开日志系统,选择服务,调整时间,猜几个关键词。找到一条报错之后,还需要判断它与这次告警有什么关系。

最近整理日志与指标的埋点实践时,我觉得最值得分享的部分,就是如何缩短这段距离:用低基数指标发现异常,用 Canonical log line 保存事件上下文,再把告警转换成一个可以继续修改的日志查询。 点击告警,就能进入对应的事件集合,开始比较和验证。

这套思路需要先把统计单位说清楚。以 webhook 为例,一次投递尝试结束时,更新尝试次数和耗时指标;同时,对需要保留证据的尝试输出一条结构化完成日志。指标与日志复用同一份结果分类和耗时计算,避免两边分别判断“什么算失败”。

对应的 Prometheus 计数器可以保留这些维度。下面只是某条时间序列的标识,不是完整告警表达式:

webhook_attempts_total{service="notification", env="prod", region="cn-east", outcome="failure"}

同一次尝试的完成日志则保留更多上下文。字段和值仅作示例:

{
  "timestamp": "2026-09-01T12:00:00Z",
  "event_name": "webhook.attempt.completed",
  "service": "notification",
  "env": "prod",
  "region": "cn-east",
  "outcome": "failure",
  "duration_ms": 3012,
  "account_id": "account-456",
  "destination": "endpoint-a",
  "attempt": 2,
  "delivery_id": "delivery-123",
  "error_type": "connect_timeout",
  "error": "connection timed out"
}

服务、环境、区域、结果在两边保持一致;账号、目标、执行身份和错误详情留在日志里。这样可以在排障时按高基数字段筛选和统计,又不必把这些字段全部变成指标标签。Prometheus 的每个不同标签组合都会形成独立时间序列,因此标签选择仍需控制。Prometheus 指标与标签命名建议

Canonical log line 已有成熟实践,Stripe 也公开分享过这种方法:在一个工作单元结束时,把关键上下文集中到一条记录里。它很适合这里的需求,因为账号、结果、目标和耗时在同一行,统计前不必先把多层零散日志拼起来。Stripe 的实践文章

这里的 completed 表示尝试结束,包含成功和失败。一次尝试失败,也不代表最终投递失败;第二次尝试、最终投递结果必须有各自清楚的含义。子操作如果有独立排障价值,可以单独记录,通过业务 ID 或 Trace ID 关联。同一份错误沿调用栈打印三次,不能算成三次业务失败。

接下来,在告警规则的下钻配置中,把指标对应到 webhook.attempt.completed,再将告警实际携带的标签和触发时间填入查询。下面是逻辑示意,具体语法由日志后端决定:

event_name = webhook.attempt.completed
service = ${alert.labels.service}
env = prod
region = ${alert.labels.region}
outcome = failure

这个例子假设告警限定生产环境,并保留了 service 和 region。如果告警按服务、区域聚合,实例标签已经消失,下钻查询就不能再引用实例。查询时间也应固定在告警发生时,覆盖当时的计算窗口,并适当留出上下文;第二天打开同一告警,仍应看到昨天的问题。变量需要校验,并按目标查询语言转义。

日志里不必反向保存指标名。 一个事件可以贡献次数、耗时等多个指标,稳定的业务事件名负责定位明细,指标与事件的映射放在下钻配置中。指标也可以直接埋点,不要求先写日志、再从日志生成所有指标。

真正开始调查后,查询必须可以修改。先看失败记录中哪些错误增加;想比较目标的失败率,就移除 outcome = failure,补查成功事件;怀疑是某个区域的问题,就扩大区域范围;发现某类错误值得深入,再选具体执行查看 Trace。顶部图中的箭头表达的就是这个调查顺序,日志本身早已在执行时采集。

这也是我更愿意把这套路径作为告警默认入口的原因。Exemplar 可以把聚合指标关联到某次测量的上下文,适合快速打开一个执行样本。但一次连接超时的 Trace,并不能说明超时就是这次失败率上升的主要原因。它可能只是许多失败类型中的一种。OpenTelemetry Exemplar 规范

如果目标是解释一次请求,样本关联很直接。如果目标是解释一次告警,我更希望先看到相关事件的分布,再决定深入哪一次执行。支持集合搜索和聚合的 Trace 后端也能提供这种调查能力,exemplar 本身则不负责定义这个查询集合。

这套实践有一个不能省略的前提:查询出来的证据覆盖了什么,必须说清楚。 对选定的、有排障价值的事件,我们按全量采集设计;如果要比较成功与失败,就同时保留两类事件。如果某条路径只有异常详情,总量和成功率仍应以指标为准。日志丢失、采样、保留期和查询截断,也都会影响结论。字段一致,不代表日志数量一定与指标计算结果严格相等。

成本控制从事件取舍开始。正常心跳、空闲轮询、没有状态变化的周期检查,如果指标已经足够回答问题,就不需要逐次写完成日志;省略日志的路径仍正常更新指标。有价值的字段应在正常运行时可用,避免排障必须先开 debug、再等故障重现。结构化字段也需要真正解析入库,并支持过滤和聚合。

这与 OpenTelemetry 可以共存。一个属性完整的业务 Span,也能承担完成日志的职责;如果后端的检索、聚合和保留策略都满足需求,没有必要再存一份相同证据。需要坚持的是事件粒度、结果口径和查询入口的一致性。

对 AI Agent,我也希望提供同一份已解析查询、告警条件和时间范围。让查询系统计算分布,Agent 获取统计结果和必要样本,再提出下一步要验证的问题。它给出的判断应附带实际执行的查询,方便工程师复查。把第一条报错扩写成根因分析,很容易遗漏决定告警走势的其他事件。

落地可以从一条常见告警开始:明确它统计什么工作单元,检查对应完成日志是否有足够上下文,再把标签和时间范围接到可编辑查询上。验证成功、失败和重试,确认分母从哪里来。先把这条路径走通,就能判断这种关联是否改善了团队的调查过程。

我希望一条告警除了告诉我“哪里异常”,还能带我找到一组可以继续分析的证据。你们的团队目前如何完成这一步?更常从日志集合开始,还是先打开一条具体 Trace?

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云