基于 Rocket 和 OpenTelemetry 构建可观测的异步看板状态机


一个HTTP POST请求过来,将看板上的卡片从“待处理”移到“进行中”,接口返回了200 OK。但故事到这里就结束了吗?在真实的生产环境中,这恰恰是问题的开始。这次状态变更到底花了多少毫秒?其中数据库锁占用了多久?校验逻辑、状态更新和事件通知,哪一步是潜在的瓶лод?如果下游的通知服务出现了延迟,我们能快速定位到是网络问题还是下游服务本身的问题吗?如果不能回答这些问题,我们的服务就像一个黑盒,稳定运行全凭运气。

这次复盘的目标,就是从零开始构建一个具备深度可观测性的看板(Kanban)核心状态机服务。我们将使用 Rust 和 Rocket 框架,但重点不在于实现一个功能齐全的看板应用,而在于将 OpenTelemetry 的可观测性能力,像血管一样植入到服务的每一个角落。我们追求的不是事后补救式的监控,而是在设计之初就将“可观测性”作为一等公民的开发模式(Observability-Driven Development)。

第一步:奠定可观测性的基石

在编写任何业务逻辑之前,首先要搭建好整个可观测性基础设施。在 Rust 生态中,这通常意味着 tracing crate 作为前端 API,与 opentelemetry crate 生态作为后端实现相结合。

Cargo.toml 中的依赖是我们的第一份蓝图:

[dependencies]
rocket = { version = "0.5.0", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.20", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid"] }
uuid = { version = "1.4", features = ["v4", "serde"] }

# Tracing & Observability Stack
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
opentelemetry = { version = "0.21", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.14", features = ["grpc-tonic"] }
tracing-opentelemetry = "0.22"
opentelemetry-semantic-conventions = "0.12"

这里的核心是 tracingtracing-subscriberopentelemetryopentelemetry-otlptracing-opentelemetry。它们的职责分工明确:

  • tracing: 提供一个统一的 API(如 span!info! 宏和 #[instrument] 属性)来在代码中埋点。
  • tracing-subscriber: 收集 tracing 产生的数据,并决定如何处理它们(例如,打印到控制台或发送到远端)。
  • opentelemetry: OpenTelemetry 标准的 Rust 实现,定义了 Trace, Metric, Log 的数据模型。
  • opentelemetry-otlp: 负责将 OpenTelemetry 数据通过 OTLP 协议导出到兼容的收集器(如 Jaeger, Grafana Tempo, Uptrace)。
  • tracing-opentelemetry: 这是一个关键的“胶水层”,它将 tracing crate 产生的 Span 数据转换成 OpenTelemetry 的数据格式。

我们的第一个代码文件不是 main.rs,而是 src/telemetry.rs。初始化全局的 Tracer Provider 是任何可观测性实践的起点。

```rust
// src/telemetry.rs

use opentelemetry::runtime::Tokio;
use opentelemetry::sdk::propagation::TraceContextPropagator;
use opentelemetry::sdk::trace;
use opentelemetry::sdk::Resource;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_semantic_conventions as semcov;
use tracing::Subscriber;
use tracing_opentelemetry::OtelData;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};

// 初始化并注册全局的 tracing subscriber 和 OpenTelemetry pipeline
pub fn init_telemetry() {
// 设置服务名等资源属性
let resource = Resource::new(vec


  目录