Table of contents
Open Table of contents
核心模型:Event + Span
tracing 这个 crate 是整个体系的中心。它只产生两种东西:
Event = 一个离散的点("发生了什么")
info!("player logged in") ← 就是一条日志
Span = 一段时间区间("正在做什么")
#[instrument]
async fn handle_login() { ... }
← 函数开始时创建 span,结束时关闭,记录了耗时
Span 可以嵌套,形成一棵树——这棵树就是 trace:
handle_login [0ms ──────── 80ms] ← span
├─ query_database [5ms ── 30ms] ← 子 span
│ info!("found player") ← event(挂在这个 span 下)
└─ cache_token [35ms ── 40ms] ← 子 span
关键洞察:tracing 不关心这些数据最终去哪。它只负责产生 event 和 span。
消费端:Subscriber + Layer
tracing 产生数据,谁来消费?Subscriber。
你的代码(生产者) Subscriber(消费者)
│
info!("hello") ─────────────► ┌──────┴──────┐
│ Layer 1 │ fmt → 打印到控制台/文件 → 这就是 Logs
#[instrument] ─────────────► ├─────────────┤
async fn foo() │ Layer 2 │ OpenTelemetry → 导出 span 树 → 这就是 Traces
├─────────────┤
│ Layer 3 │ 过滤器 → 控制哪些级别/模块要记录
└─────────────┘
Layer 是可叠加的。同一个 event/span 会经过所有 layer——所以你写一次 info!(),它既能变成控制台日志,又能变成 trace 中的一个节点。
这就是 Rust 可观测性的核心设计:一次埋点,多路输出。
Metrics 是独立的
Metrics 和 tracing 分开——它不是”事件”,是”聚合数值”。用 metrics crate 单独记录:
counter!("requests_total").increment(1); ← 计数器加 1
histogram!("request_duration").record(0.05); ← 记录一个延迟值
metrics crate 的架构和 tracing 一样:代码里记录数值,exporter 消费并暴露成 HTTP 端点给 Prometheus 来抓取。
整体关系
你的业务代码
│
├── tracing 宏 ──────────► tracing subscriber
│ info!() │
│ #[instrument] ├─► fmt layer ──────────► stdout/文件 = Logs
│ └─► otel layer ─── OTLP ──► Jaeger = Traces
│
└── metrics 宏 ──────────► metrics exporter
counter!() │
histogram!() └─► /metrics HTTP ──────► Prometheus = Metrics
三者的数据最终都汇聚到 Grafana 看板
trace_id 是串联它们的线索
所有配置代码在做的事
observability.md 里那一堆初始化代码,本质上就三件事:
1. 配 fmt layer → 日志输出什么格式(JSON/pretty)、输出到哪(stdout/文件)
2. 配 otel layer → trace 的 span 导出到哪个后端(Jaeger/Tempo)
3. 配 metrics exporter → 指标暴露给谁(Prometheus 来抓的 HTTP 端点)
业务代码里永远只用这几个宏,不需要关心后面的管道:
// Logs
info!(player_id = %id, "player logged in");
// Traces(自动生成 span)
#[instrument]
async fn handle_login() { ... }
// Metrics
counter!("requests_total").increment(1);
三者如何串联排查问题
① Grafana 看板: grpc_errors_total 飙升 (Metrics)
│
② 找到异常时间段的 trace_id (Metrics → Traces)
│
③ Jaeger 中展开 trace,看到 pg_query span 耗时 5s (Traces)
│
④ 用 trace_id 在 Loki 搜日志 (Traces → Logs)
→ "ERROR: deadlock detected"
│
⑤ 定位 root cause
关联的关键:trace_id 贯穿三者。tracing + tracing-opentelemetry 自动在每条日志中注入当前 span 的 trace_id,Grafana 可以一键从 Metrics 跳到 Traces 再跳到 Logs。