跳转到正文
zeno's blog
返回

可观测性(二):Rust 可观测性体系的架构理解

专题: 可观测性

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。


分享这篇文章:

上一篇
hyper(一):底层 HTTP 实现与 1.0 迁移
下一篇
axum(三):中间件与生产实践-tower 原生的 Web 应用