告警响了,指标告诉你「P99 延迟从 200ms 涨到 2s」,但到底是哪几个请求慢、慢在哪个环节,指标本身永远回答不了。Exemplar 就是为了打通这道墙而生的:它把个别 trace 的 ID 作为「样本」挂在指标的时间序列上,让你能从一条聚合曲线直接跳到一次真实的请求链路。本文从 Exemplar 的规范与存储,讲到客户端埋点、Prometheus 采集、Grafana 关联配置与 TraceID/日志的双向打通。
关键概念:Exemplar=附着在指标样本上的「示例引用」,通常是 trace_id。它让聚合指标保留「指向个体」的能力——指标看趋势,Exemplar 带你找到具体的那个慢请求。
- 1. 三支柱割裂与关联的必要性
- 2. Exemplar 原理与 OpenMetrics 规范
- 3. Prometheus 与客户端埋点实践
- 4. 从指标跳转到 trace 的工程实现
- 5. Trace 与日志的双向关联
- 6. Grafana 关联跳转与钻取
- 7. 常见避坑
- 8. 最佳实践清单
1. 三支柱割裂与关联的必要性
1.1 割裂的典型症状
现象一:看指标发现延迟飙升 → 切到链路系统 → 不知道查哪个时间点
现象二:看到一条慢 trace → 想找"同一时刻还有多少条慢" → 指标里查不到
现象三:日志里有错误 → 想关联到指标趋势 → 只能靠人肉对时间
根因:三套系统各自为政,缺少"可跳转的关联键"
1.2 关联的三条路径
指标 → 链路:Exemplar(指标样本携带 trace_id)
链路 → 日志:TraceID 注入日志(log-trace correlation)
日志 → 链路:日志中提取 TraceID 反查链路
闭环目标:任一入口都能在 3 次点击内到达根因
1.3 关联键的选择
| 关联方向 | 关联键 | 实现方式 |
|---|---|---|
| 指标→链路 | trace_id | Exemplar |
| 链路→日志 | trace_id + span_id | 日志注入上下文 |
| 日志→指标 | service + 时间 | 统一标签与时间对齐 |
| 指标→日志 | service + 时间 | 数据链接 |
核心原则:trace_id 是唯一贯穿三者的"主键",务必全程透传
2. Exemplar 原理与 OpenMetrics 规范
2.1 Exemplar 是什么
定义:附着在某个指标样本上的、带时间戳的额外信息
典型形态:一个直方图桶的样本 + 一条 trace_id
价值:聚合指标不再"只见森林",能指向具体的"一棵树"
适用指标类型:
Histogram:最常用,每个桶样本可带 exemplar
Counter:也可带,但语义较弱
不适用:Gauge(无累积语义,关联意义小)
2.2 OpenMetrics 文本格式
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.5"} 24054 # {trace_id="a1b2c3"} 0.42 1696233600.123
http_request_duration_seconds_bucket{le="1.0"} 24110 # {trace_id="d4e5f6"} 0.91 1696233601.456
http_request_duration_seconds_sum 53423.0
http_request_duration_seconds_count 24110
语法要点:
# {key="value"} value timestamp → exemplar 附着在样本行尾
trace_id 是约定俗成的 key,也可以是 span_id / request_id
只有 OpenMetrics 格式支持,旧版 Prometheus 文本格式不支持
2.3 存储与生命周期
Prometheus:exemplar 单独存储(exemplar storage),默认保留 5 分钟
内存环形缓冲区,容量由 --storage.exemplars 控制
不会长期保留——exemplar 是"近期样本",不是历史档案
远端写入:需开启 send_exemplars,且远端(Thanos/Mimir)需支持
Grafana:只展示 exemplar 存在的窗口,过期后自动消失
⚠️ 注意:exemplar 默认只保留几分钟,看到告警后再去翻几小时前的 exemplar 通常已经没了。要么调大保留,要么依赖长期存储。
3. Prometheus 与客户端埋点实践
3.1 服务端开启 exemplar
启动参数:
--enable-feature=exemplar-storage
远端写入:
remote_write:
- url: http://mimir:9009/api/v1/push
send_exemplars: true
3.2 Go 客户端埋点
import (
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
)
hist := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration",
Buckets: prometheus.DefBuckets,
},
[]string{"route", "method", "status"},
)
// 在处理函数里把当前 span 的 trace_id 作为 exemplar 附上
func observe(ctx context.Context, h prometheus.Observer, dur float64) {
sc := trace.SpanContextFromContext(ctx)
if sc.IsValid() {
h.(prometheus.ExemplarObserver).ObserveWithExemplar(
dur,
prometheus.Labels{"trace_id": sc.TraceID().String()},
)
return
}
h.Observe(dur)
}
3.3 OpenTelemetry 的自动关联
OTel Prometheus Exporter 支持自动把当前 span 的 trace_id 作为 exemplar
条件:指标记录时 context 中存在有效 span
配置:WithExemplarSetting / ExemplarFilter
好处:不用手写 ObserveWithExemplar,框架自动完成
注意:exemplar 数量按采样比控制,不要每个请求都记
3.4 采样策略
exemplar 不是越多越好:
- 高流量服务每个请求都带 → 存储与传输压力大
- 建议按固定比例(如 1%)或仅对慢请求/错误请求记录
- 关注价值:慢请求和错误的 trace 最有诊断意义
4. 从指标跳转到 trace 的工程实现
4.1 跳转链路
Grafana 指标面板(直方图)
→ 鼠标悬停出现 exemplar 点(菱形)
→ 点击携带 trace_id
→ 通过数据链接跳转到 Tempo/Jaeger
→ 打开该 trace 详情
4.2 Grafana 数据链接配置
面板 Data links:
Title: 查看 Trace
URL: http://tempo.observability:3200/trace/${__value.raw}
# 或使用 TraceID 变量
URL: /explore?left={"datasource":"tempo","queries":[{"query":"${__value.raw}"}]}
要点:
- 目标数据源需在 Grafana 中注册(Tempo / Jaeger)
- 变量名与 exemplar 的 label key 一致(trace_id)
4.3 全链路闭环示例
1. SLO 面板显示 P99 超标(指标)
2. 悬停直方图 P99 桶 → 看到 exemplar 菱形点
3. 点击 → 跳到 Tempo,看到这条慢 trace
4. trace 中某 span 异常 → 点 span → 跳转该服务的日志
5. 日志中看到具体报错 → 定位根因
4.4 没有 Exemplar 时的替代方案
替代一:exemplar 不可用 → 用 trace 系统的"慢查询"入口
按 service + 时间 + 耗时阈值直接搜 trace
替代二:指标与 trace 时间对齐,人工缩小窗口
替代三:在指标 label 中带 trace_id(高基数,不推荐)
结论:替代方案都更笨重,Exemplar 是成本最低的关联方式
5. Trace 与日志的双向关联
5.1 链路 → 日志:注入 TraceID
做法:日志框架在每条日志里自动带上 trace_id / span_id
Go 示例(slog + OTel):
handler := slog.NewJSONHandler(os.Stdout, nil)
logger := slog.New(handler)
sc := trace.SpanContextFromContext(ctx)
logger.Info("payment failed",
"trace_id", sc.TraceID().String(),
"span_id", sc.SpanID().String())
5.2 日志 → 链路:反查
做法:日志系统中提取 trace_id 字段,配置跳转链接
Grafana Loki:
派生字段(derived field)正则提取 trace_id
→ 生成跳转到 Tempo 的链接
效果:在日志里点 trace_id 直接打开对应链路
5.3 结构化日志与语义约定
字段命名统一(OTel 语义约定):
trace_id, span_id 固定小写下划线
不要用 traceId / TraceID / trace 混用
好处:Loki、ES 等日志系统可用同一套提取规则
ℹ️ 核心:日志与链路的关联靠 TraceID 注入,链路与指标的关联靠 Exemplar。三者一旦串起来,排障路径就变成「看指标 → 点 exemplar → 看 trace → 点 trace_id → 看日志」。
6. Grafana 关联跳转与钻取
6.1 三种关联机制
1. Data links 面板内值 → 外部 URL
2. Exemplars 直方图点 → trace 系统
3. Correlations 数据源之间的关联配置(Grafana 9+)
Correlations 可在 Loki/Metrics 间定义"用 trace_id 关联"
6.2 Correlations 配置
在 Grafana 数据源设置里添加 Correlation:
Source: Loki(日志)
Target: Tempo(链路)
Label: trace_id
URL: ${__value.raw}
效果:日志行中的 trace_id 自动变成可点击链接
6.3 统一 Explore 体验
Grafana Explore 支持 Split 视图:
左:指标(Prometheus)
右:链路(Tempo)
同一时间窗口,点击 exemplar 双向联动
建议:把常用关联固化成 Dashboard 变量与链接,降低排障门槛
6.4 权限与网络
跳转依赖浏览器可达后端数据源:
- Tempo/Jaeger 需对 Grafana 前端可达(或走代理)
- 跨集群时注意网络策略与鉴权
- 建议统一入口,避免多处配置漂移
7. 常见避坑
| 坑 | 现象 | 对策 |
|---|---|---|
| 未开 exemplar 存储 | 直方图上没有菱形点 | 加 –enable-feature=exemplar-storage |
| exemplar 过期 | 告警后翻不到旧样本 | 调大保留或依赖远端长期存储 |
| 全量记录 exemplar | 存储与带宽暴涨 | 按比例或只对慢/错请求记录 |
| trace_id 命名不一致 | 跳转链接取不到值 | 统一 trace_id 小写命名 |
| 远端未开 send_exemplars | 中心端看不到 exemplar | remote_write 显式开启 |
| 日志未注入 TraceID | 无法从日志跳链路 | 日志中间件统一注入上下文 |
| 跳转后端不可达 | 点击后 404/超时 | 检查网络策略与数据源注册 |
| 只做单向关联 | 排障仍需人工切换 | 三向打通,形成闭环 |
8. 最佳实践清单
□ 开启 exemplar 存储并配置远端写入 send_exemplars
□ 用 OpenTelemetry 自动注入 trace_id,减少手工埋点
□ exemplar 按比例采样,优先记录慢请求与错误请求
□ 统一关联键命名:trace_id / span_id 全小写下划线
□ 日志框架统一注入 trace_id,不依赖人工打印
□ 在 Grafana 配置 Data links 与 Correlations 打通三向跳转
□ 保证 Tempo/Jaeger 对 Grafana 前端可达
□ 排障 SOP 明确「指标→exemplar→trace→日志」路径
□ 定期验证关联链路(造一条慢请求走通全流程)
□ 关注 exemplar 保留窗口,关键故障窗口及时留存
一句话原则
指标看趋势、Exemplar 指个体、TraceID 串日志——
三向关联打通,排障从"猜"变成"点"。
小结
指标、链路、日志的关联不是锦上添花,而是把三套系统从并列变成一体的关键工程。核心只有两条纽带:Exemplar 把 trace_id 挂到指标样本上,实现「指标 → 链路」;TraceID 注入把链路上下文写进日志,实现「链路 ↔ 日志」。落地时注意:开启 exemplar 存储与远端转发、用 OpenTelemetry 自动埋点、统一关联键命名、在 Grafana 配好 Data links 与 Correlations。当三者真正打通,排障就变成「看趋势 → 点样本 → 看链路 → 点 ID → 看日志」的四步闭环,平均定位时间(MTTR)会显著下降。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。