Rust 语言 OpenTelemetry 接入指南
本文档提供 Rust 应用接入 OpenTelemetry 的完整指南,包括 SDK 手动接入和框架集成示例,支持高性能异步运行时。
目录
概述
Rust 语言特性
Rust 的 OpenTelemetry 支持具有以下特点:
- 高性能: 零成本抽象,极低的性能开销
- 内存安全: 编译时保证内存安全
- 异步支持: 完美支持 async/await 和异步运行时
- 类型安全: 强类型系统,编译时错误检查
- 并发安全: 通过所有权系统保证并发安全
版本要求
- Rust 版本: 1.70+ (推荐 1.75+)
- OpenTelemetry Rust: v0.22.0+
- 支持架构: x86_64, arm64, aarch64
支持的信号
- ✅ Traces: 完全支持
- ✅ Metrics: 完全支持
- ✅ Logs: 完全支持(通过 tracing 库)
适用场景
- 高性能 Web 服务
- 微服务架构
- 系统编程
- 异步应用
- 实时系统
快速开始
1. 添加依赖
在 Cargo.toml 中添加:
[dependencies]opentelemetry = "0.22"opentelemetry-sdk = "0.22"opentelemetry-otlp = "0.15"opentelemetry-semantic-conventions = "0.15"tokio = { version = "1", features = ["full"] }tracing = "0.1"tracing-opentelemetry = "0.22"tracing-subscriber = "0.3"
2. 基础初始化
// main.rsuse opentelemetry::global;use opentelemetry_sdk::trace::TracerProvider;use opentelemetry_sdk::Resource;use opentelemetry_sdk::trace::Config;use opentelemetry_otlp::WithExportConfig;use opentelemetry_semantic_conventions::resource::SERVICE_NAME;fn init_tracer() -> TracerProvider {let resource = Resource::new(vec![SERVICE_NAME.string("my-rust-service"),]);let exporter = opentelemetry_otlp::new_exporter().tonic().with_endpoint("http://uptrace-grpc.yodao.cn:4317").with_headers([("uptrace-dsn".to_string(), "http://token@uptrace-grpc.yodao.cn/project?grpc=4317".to_string())].into());TracerProvider::builder().with_config(Config::default().with_resource(resource)).with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio).build()}#[tokio::main]async fn main() {let _tracer_provider = init_tracer();// 您的应用代码println!("Hello, OpenTelemetry!");}
3. 配置环境变量
# 服务信息export OTEL_SERVICE_NAME=my-rust-serviceexport OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production# OTLP Exporter(内部平台)export OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:4317export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317# Traces 配置export OTEL_TRACES_EXPORTER=otlpexport OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1# Metrics 配置export OTEL_METRICS_EXPORTER=otlp
SDK 手动接入
安装依赖
在 Cargo.toml 中添加:
[dependencies]opentelemetry = "0.22"opentelemetry-api = "0.22"opentelemetry-sdk = "0.22"opentelemetry-otlp = "0.15"opentelemetry-semantic-conventions = "0.15"tracing = "0.1"tracing-opentelemetry = "0.22"tracing-subscriber = { version = "0.3", features = ["env-filter"] }tokio = { version = "1", features = ["full"] }
基础初始化
创建 OpenTelemetry 初始化函数
// otel.rsuse opentelemetry::global;use opentelemetry_sdk::trace::TracerProvider;use opentelemetry_sdk::Resource;use opentelemetry_sdk::trace::Config;use opentelemetry_otlp::WithExportConfig;use opentelemetry_semantic_conventions::resource::SERVICE_NAME;use std::env;pub fn init_opentelemetry() -> TracerProvider {let service_name = env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| "my-rust-service".to_string());let resource = Resource::new(vec![SERVICE_NAME.string(service_name.clone()),]);let endpoint = env::var("OTEL_EXPORTER_OTLP_ENDPOINT").unwrap_or_else(|_| "http://uptrace-grpc.yodao.cn:4317".to_string());let dsn = env::var("OTEL_EXPORTER_OTLP_HEADERS").unwrap_or_else(|_| "http://token@uptrace-grpc.yodao.cn/project?grpc=4317".to_string());let exporter = opentelemetry_otlp::new_exporter().tonic().with_endpoint(endpoint).with_headers([("uptrace-dsn".to_string(), dsn)].into());let tracer_provider = TracerProvider::builder().with_config(Config::default().with_resource(resource)).with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio).build();global::set_tracer_provider(tracer_provider.clone());tracer_provider}
在应用中使用
// main.rsmod otel;use otel::init_opentelemetry;use opentelemetry::global;use opentelemetry::trace::Tracer;#[tokio::main]async fn main() {let _tracer_provider = init_opentelemetry();let tracer = global::tracer("my-service");let mut span = tracer.start("handle_request");span.set_attribute(opentelemetry::KeyValue::new("http.method", "GET"));span.set_attribute(opentelemetry::KeyValue::new("http.url", "/"));// 业务逻辑println!("Hello, World!");span.end();}
Traces - 手动埋点
创建和使用 Span
use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn process_order(order_id: String) -> Result<(), Box<dyn std::error::Error>> {let tracer = global::tracer("my-service");let mut span = tracer.start("process_order");span.set_attribute(opentelemetry::KeyValue::new("order.id", order_id.clone()));span.set_attribute(opentelemetry::KeyValue::new("operation", "process"));span.add_event("order_validation_started", vec![]);validate_order(&order_id).await?;span.add_event("order_validated", vec![]);charge_payment(&order_id).await?;span.add_event("payment_processed", vec![]);span.set_status(Status::Ok);span.set_attribute(opentelemetry::KeyValue::new("order.success", true));span.end();Ok(())}
Context 传播
use opentelemetry::global;use opentelemetry::trace::{Tracer, TraceContextExt};use opentelemetry::Context;use opentelemetry::propagation::TextMapPropagator;async fn make_http_request(url: &str) -> Result<String, Box<dyn std::error::Error>> {let tracer = global::tracer("my-service");let mut span = tracer.start("http_request");span.set_attribute(opentelemetry::KeyValue::new("http.url", url));span.set_attribute(opentelemetry::KeyValue::new("http.method", "GET"));let cx = Context::current_with_span(span);// 注入 Context 到 HTTP headerslet mut headers = reqwest::header::HeaderMap::new();global::get_text_map_propagator(|propagator| {propagator.inject_context(&cx, &mut HeaderInjector(&mut headers));});let client = reqwest::Client::new();let response = client.get(url).headers(headers).send().await?;let status = response.status();span.set_attribute(opentelemetry::KeyValue::new("http.status_code", status.as_u16()));if status.is_success() {span.set_status(Status::Ok);} else {span.set_status(Status::error("HTTP request failed"));}span.end();Ok(response.text().await?)}struct HeaderInjector<'a>(&'a mut reqwest::header::HeaderMap);impl<'a> opentelemetry::propagation::Injector for HeaderInjector<'a> {fn set(&mut self, key: &str, value: String) {if let Ok(key) = reqwest::header::HeaderName::from_bytes(key.as_bytes()) {self.0.insert(key, value.parse().unwrap());}}}
Metrics - 指标采集
Counter(计数器)
use opentelemetry::global;use opentelemetry::metrics::Meter;fn handle_request(meter: &Meter, method: &str, route: &str, status_code: u16) {let request_counter = meter.u64_counter("http.server.requests").with_description("Total number of HTTP requests").init();request_counter.add(1,&[opentelemetry::KeyValue::new("http.method", method),opentelemetry::KeyValue::new("http.route", route),opentelemetry::KeyValue::new("http.status_code", status_code.to_string()),],);}
Histogram(直方图)
fn record_duration(meter: &Meter, duration: f64, method: &str, route: &str) {let request_duration = meter.f64_histogram("http.server.duration").with_description("HTTP request duration").with_unit(opentelemetry::metrics::Unit::new("ms")).init();request_duration.record(duration,&[opentelemetry::KeyValue::new("http.method", method),opentelemetry::KeyValue::new("http.route", route),],);}
UpDownCounter(升降计数器)
fn connection_opened(meter: &Meter) {let active_connections = meter.i64_up_down_counter("http.server.active_connections").with_description("Number of active HTTP connections").init();active_connections.add(1, &[]);}fn connection_closed(meter: &Meter) {let active_connections = meter.i64_up_down_counter("http.server.active_connections").with_description("Number of active HTTP connections").init();active_connections.add(-1, &[]);}
Logs - 日志集成
集成 tracing 库
use tracing::{info, error};use tracing_subscriber::layer::SubscriberExt;use tracing_subscriber::util::SubscriberInitExt;use tracing_opentelemetry::OpenTelemetryLayer;use opentelemetry::global;fn init_tracing() {let tracer = global::tracer("my-service");let telemetry = OpenTelemetryLayer::new(tracer);tracing_subscriber::registry().with(telemetry).with(tracing_subscriber::EnvFilter::from_default_env()).init();}#[tokio::main]async fn main() {init_tracing();info!("Application started");// 业务逻辑match process_order("12345".to_string()).await {Ok(_) => info!("Order processed successfully"),Err(e) => error!(error = %e, "Failed to process order"),}}
框架集成示例
Actix Web 框架集成
use actix_web::{web, App, HttpServer, HttpResponse};use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};use opentelemetry::trace::TraceContextExt;async fn get_user(path: web::Path<String>) -> HttpResponse {let tracer = global::tracer("actix-web");let mut span = tracer.start("get_user");span.set_attribute(opentelemetry::KeyValue::new("user.id", path.into_inner()));// 业务逻辑let user = fetch_user(&span).await;span.set_status(Status::Ok);span.end();HttpResponse::Ok().json(user)}#[actix_web::main]async fn main() -> std::io::Result<()> {let _tracer_provider = init_opentelemetry();HttpServer::new(|| {App::new().route("/users/{id}", web::get().to(get_user))}).bind("127.0.0.1:8080")?.run().await}
Axum 框架集成
use axum::{extract::Path, response::Json, routing::get, Router};use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};use tower::ServiceBuilder;use tower_http::trace::TraceLayer;async fn get_user(Path(id): Path<String>) -> Json<serde_json::Value> {let tracer = global::tracer("axum");let mut span = tracer.start("get_user");span.set_attribute(opentelemetry::KeyValue::new("user.id", id.clone()));// 业务逻辑let user = fetch_user(&id).await;span.set_status(Status::Ok);span.end();Json(user)}#[tokio::main]async fn main() {let _tracer_provider = init_opentelemetry();let app = Router::new().route("/users/:id", get(get_user)).layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()));let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();axum::serve(listener, app).await.unwrap();}
Warp 框架集成
use warp::Filter;use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn get_user(id: String) -> Result<impl warp::Reply, warp::Rejection> {let tracer = global::tracer("warp");let mut span = tracer.start("get_user");span.set_attribute(opentelemetry::KeyValue::new("user.id", id.clone()));// 业务逻辑let user = fetch_user(&id).await;span.set_status(Status::Ok);span.end();Ok(warp::reply::json(&user))}#[tokio::main]async fn main() {let _tracer_provider = init_opentelemetry();let get_user_route = warp::path("users").and(warp::path::param()).and_then(get_user);warp::serve(get_user_route).run(([0, 0, 0, 0], 3030)).await;}
数据库集成
SQLx 集成
use sqlx::PgPool;use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn query_database(pool: &PgPool, query: &str) -> Result<Vec<serde_json::Value>, sqlx::Error> {let tracer = global::tracer("my-service");let mut span = tracer.start("database.query");span.set_attribute(opentelemetry::KeyValue::new("db.system", "postgresql"));span.set_attribute(opentelemetry::KeyValue::new("db.statement", query));let result = sqlx::query(query).fetch_all(pool).await;match &result {Ok(_) => span.set_status(Status::Ok),Err(e) => {span.set_status(Status::error(e.to_string()));}}span.end();result.map(|rows| {rows.iter().map(|row| serde_json::json!({})).collect()})}
MongoDB 驱动集成
use mongodb::Client;use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn query_mongodb(client: &Client,db_name: &str,collection: &str,filter: bson::Document,) -> Result<Vec<bson::Document>, mongodb::error::Error> {let tracer = global::tracer("my-service");let mut span = tracer.start("mongodb.query");span.set_attribute(opentelemetry::KeyValue::new("db.system", "mongodb"));span.set_attribute(opentelemetry::KeyValue::new("db.name", db_name));span.set_attribute(opentelemetry::KeyValue::new("db.collection", collection));let db = client.database(db_name);let coll = db.collection::<bson::Document>(collection);let result = coll.find(filter, None).await;match &result {Ok(_) => span.set_status(Status::Ok),Err(e) => {span.set_status(Status::error(e.to_string()));}}span.end();result.and_then(|mut cursor| async {let mut docs = Vec::new();while let Some(doc) = cursor.next().await.transpose()? {docs.push(doc);}Ok(docs)}).await}
Redis 集成
use redis::Commands;use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn get_cache(conn: &mut redis::Connection, key: &str) -> Result<String, redis::RedisError> {let tracer = global::tracer("my-service");let mut span = tracer.start("redis.get");span.set_attribute(opentelemetry::KeyValue::new("db.system", "redis"));span.set_attribute(opentelemetry::KeyValue::new("db.operation", "GET"));span.set_attribute(opentelemetry::KeyValue::new("db.redis.key", key));let result: Result<String, _> = conn.get(key);match &result {Ok(_) => span.set_status(Status::Ok),Err(e) => {span.set_status(Status::error(e.to_string()));}}span.end();result}
异步运行时集成
Tokio 集成
use tokio::task;use opentelemetry::global;use opentelemetry::trace::Tracer;#[tokio::main]async fn main() {let _tracer_provider = init_opentelemetry();let tracer = global::tracer("my-service");// 在异步任务中创建 spanlet handle = task::spawn(async move {let mut span = tracer.start("async_task");span.set_attribute(opentelemetry::KeyValue::new("task.name", "background_job"));// 异步业务逻辑tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;span.end();});handle.await.unwrap();}
async-std 集成
use async_std::task;use opentelemetry::global;use opentelemetry::trace::Tracer;#[async_std::main]async fn main() {let _tracer_provider = init_opentelemetry();let tracer = global::tracer("my-service");task::spawn(async {let mut span = tracer.start("async_task");// 异步业务逻辑async_std::task::sleep(std::time::Duration::from_secs(1)).await;span.end();}).await;}
消息队列集成
Kafka 集成
use rdkafka::producer::{FutureProducer, FutureRecord};use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn send_kafka_message(producer: &FutureProducer,topic: &str,message: &str,) -> Result<(), Box<dyn std::error::Error>> {let tracer = global::tracer("my-service");let mut span = tracer.start("kafka.produce");span.set_attribute(opentelemetry::KeyValue::new("messaging.system", "kafka"));span.set_attribute(opentelemetry::KeyValue::new("messaging.destination", topic));span.set_attribute(opentelemetry::KeyValue::new("messaging.operation", "publish"));let record = FutureRecord::to(topic).payload(message).key("key");match producer.send(record, std::time::Duration::from_secs(0)).await {Ok(_) => {span.set_status(Status::Ok);Ok(())}Err((e, _)) => {span.set_status(Status::error(e.to_string()));Err(Box::new(e))}}}
RabbitMQ 集成
use lapin::{Connection, ConnectionProperties, Channel};use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};async fn publish_message(channel: &Channel,exchange: &str,routing_key: &str,message: &[u8],) -> Result<(), Box<dyn std::error::Error>> {let tracer = global::tracer("my-service");let mut span = tracer.start("rabbitmq.publish");span.set_attribute(opentelemetry::KeyValue::new("messaging.system", "rabbitmq"));span.set_attribute(opentelemetry::KeyValue::new("messaging.destination", exchange));span.set_attribute(opentelemetry::KeyValue::new("messaging.rabbitmq.routing_key", routing_key));let confirm = channel.basic_publish(exchange, routing_key, Default::default(), message, Default::default()).await?;confirm.await??;span.set_status(Status::Ok);span.end();Ok(())}
HTTP 客户端集成
reqwest 集成
use reqwest::Client;use opentelemetry::global;use opentelemetry::trace::{Tracer, Status};use opentelemetry::propagation::Injector;struct HeaderInjector<'a>(&'a mut reqwest::header::HeaderMap);impl<'a> Injector for HeaderInjector<'a> {fn set(&mut self, key: &str, value: String) {if let Ok(key) = reqwest::header::HeaderName::from_bytes(key.as_bytes()) {self.0.insert(key, value.parse().unwrap());}}}async fn make_http_request(url: &str) -> Result<String, Box<dyn std::error::Error>> {let tracer = global::tracer("my-service");let mut span = tracer.start("http_request");span.set_attribute(opentelemetry::KeyValue::new("http.url", url));span.set_attribute(opentelemetry::KeyValue::new("http.method", "GET"));let cx = opentelemetry::Context::current_with_span(span);let client = Client::new();let mut request = client.get(url);// 注入 Context 到 HTTP headerslet mut headers = reqwest::header::HeaderMap::new();global::get_text_map_propagator(|propagator| {propagator.inject_context(&cx, &mut HeaderInjector(&mut headers));});request = request.headers(headers);let response = request.send().await?;let status = response.status();let mut span = cx.span();span.set_attribute(opentelemetry::KeyValue::new("http.status_code", status.as_u16()));if status.is_success() {span.set_status(Status::Ok);} else {span.set_status(Status::error("HTTP request failed"));}span.end();Ok(response.text().await?)}
高级配置
自定义采样策略
use opentelemetry_sdk::trace::Sampler;use opentelemetry::trace::SamplingResult;use opentelemetry::Context;struct CustomSampler {always_sample: Vec<String>,never_sample: Vec<String>,}impl Sampler for CustomSampler {fn should_sample(&self,_parent_context: &Context,_trace_id: u128,span_name: &str,_span_kind: &opentelemetry::trace::SpanKind,_attributes: &[opentelemetry::KeyValue],_links: &[opentelemetry::trace::Link],) -> SamplingResult {// 总是采样for pattern in &self.always_sample {if span_name.contains(pattern) {return SamplingResult {decision: opentelemetry::trace::SamplingDecision::RecordAndSample,attributes: vec![],trace_state: Default::default(),};}}// 从不采样for pattern in &self.never_sample {if span_name.contains(pattern) {return SamplingResult {decision: opentelemetry::trace::SamplingDecision::Drop,attributes: vec![],trace_state: Default::default(),};}}// 默认采样率 10%SamplingResult {decision: opentelemetry::trace::SamplingDecision::RecordAndSample,attributes: vec![],trace_state: Default::default(),}}}
性能优化配置
use opentelemetry_sdk::trace::BatchConfig;let batch_config = BatchConfig::default().with_max_export_batch_size(512).with_scheduled_delay(std::time::Duration::from_millis(5000)).with_max_queue_size(2048).with_export_timeout(std::time::Duration::from_secs(30));let tracer_provider = TracerProvider::builder().with_config(Config::default().with_resource(resource)).with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio).with_batch_config(batch_config).build();
多 Exporter 配置
use opentelemetry_otlp::WithExportConfig;use opentelemetry_stdout::SpanExporter;// 创建多个 exporterlet otlp_exporter = opentelemetry_otlp::new_exporter().tonic().with_endpoint("http://uptrace-grpc.yodao.cn:4317").with_headers([("uptrace-dsn".to_string(), "http://token@uptrace-grpc.yodao.cn/project?grpc=4317".to_string())].into());let console_exporter = SpanExporter::default();// 创建多个 processorlet otlp_processor = opentelemetry_sdk::trace::BatchSpanProcessor::builder(otlp_exporter,opentelemetry_sdk::runtime::Tokio,).build();let console_processor = opentelemetry_sdk::trace::SimpleSpanProcessor::new(console_exporter);// 创建 TracerProviderlet tracer_provider = TracerProvider::builder().with_config(Config::default().with_resource(resource)).with_span_processor(otlp_processor).with_span_processor(console_processor).build();
资源检测器配置
use opentelemetry_sdk::resource::ResourceDetector;use opentelemetry_sdk::Resource;// 使用多个资源检测器let resource = Resource::from_detectors(std::time::Duration::from_secs(0),vec![Box::new(opentelemetry_sdk::resource::EnvResourceDetector::new()),Box::new(opentelemetry_sdk::resource::OsResourceDetector),Box::new(opentelemetry_sdk::resource::ProcessResourceDetector),],).merge(&Resource::new(vec![SERVICE_NAME.string("my-rust-service"),SERVICE_VERSION.string("1.0.0"),]));
最佳实践
1. Context 传递(在异步环境中)
// ✅ 正确:在异步函数中传递 contextasync fn process_order(context: Context, order_id: String) -> Result<(), Box<dyn std::error::Error>> {let tracer = global::tracer("my-service");let span = tracer.start_with_context("process_order", &context);let new_context = context.with_span(span);// 传递 context 给下游函数validate_order(&new_context, &order_id).await?;charge_payment(&new_context, &order_id).await?;Ok(())}
2. 错误处理
// ✅ 正确:记录错误并设置状态match process_order(order_id).await {Ok(_) => span.set_status(Status::Ok),Err(e) => {span.record_error(&e);span.set_status(Status::error(e.to_string()));}}
3. 资源管理
// ✅ 正确:使用 Drop trait 确保 span 结束struct ScopedSpan {span: opentelemetry::trace::Span,}impl Drop for ScopedSpan {fn drop(&mut self) {self.span.end();}}fn operation() {let tracer = global::tracer("my-service");let span = tracer.start("operation");let _scoped = ScopedSpan { span };// 业务逻辑}
环境变量配置
OpenTelemetry Rust 支持通过环境变量配置,无需修改代码:
# 服务信息export OTEL_SERVICE_NAME=my-rust-serviceexport OTEL_SERVICE_VERSION=1.0.0export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production# OTLP Exporter(内部平台)export OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:4317export OTEL_EXPORTER_OTLP_PROTOCOL=grpc/protobufexport OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317# Traces 配置export OTEL_TRACES_EXPORTER=otlpexport OTEL_TRACES_SAMPLER=parentbased_traceidratioexport OTEL_TRACES_SAMPLER_ARG=0.1# Metrics 配置export OTEL_METRICS_EXPORTER=otlpexport OTEL_METRIC_EXPORT_INTERVAL=60000# Logs 配置export OTEL_LOGS_EXPORTER=otlpexport OTEL_LOG_LEVEL=info
支持的自动埋点库
虽然 Rust 没有官方的零代码接入方式,但社区提供了一些自动埋点库:
- tower-http: HTTP 中间件自动埋点
- tracing-opentelemetry: tracing 库集成
- opentelemetry-instrumentation: 社区维护的自动埋点库
使用 tracing-opentelemetry
use tracing_subscriber::layer::SubscriberExt;use tracing_subscriber::util::SubscriberInitExt;use tracing_opentelemetry::OpenTelemetryLayer;fn init_tracing() {let tracer = init_opentelemetry();let telemetry = OpenTelemetryLayer::new(tracer);tracing_subscriber::registry().with(telemetry).with(tracing_subscriber::EnvFilter::from_default_env()).init();}#[tokio::main]async fn main() {init_tracing();// 使用 tracing 宏自动创建 spantracing::info!("Application started");#[tracing::instrument]async fn process_order(order_id: String) {tracing::info!(order_id = %order_id, "Processing order");// 业务逻辑}process_order("12345".to_string()).await;}
故障排查
常见问题
1. 数据未发送到后端
排查步骤:
- 检查环境变量:
echo $OTEL_SERVICE_NAMEecho $OTEL_EXPORTER_OTLP_ENDPOINT
- 检查 tracer provider 是否初始化:
let tracer = global::tracer("test");// 如果 tracer provider 未初始化,会使用 NoOpTracer
- 检查网络连接:
// 测试端点是否可达let client = reqwest::Client::new();let response = client.get("http://uptrace-grpc.yodao.cn:4317").send().await?;
2. Span 未关联
确保正确传递 context:
let new_context = context.with_span(span);downstream_function(&new_context).await;
3. 异步 Context 丢失
在异步环境中,确保使用正确的 context 传播:
let context = Context::current();tokio::spawn(async move {// 在新的异步任务中使用 contextlet span = tracer.start_with_context("async_task", &context);// ...});
4. 编译错误
确保在 Cargo.toml 中正确配置依赖:
[dependencies]opentelemetry = { version = "0.22", features = ["trace", "metrics"] }opentelemetry-otlp = { version = "0.15", features = ["trace", "metrics", "tonic"] }
调试技巧
启用详细日志
export RUST_LOG=debugexport OTEL_LOG_LEVEL=debug
使用 Console Exporter
use opentelemetry_stdout::SpanExporter;let exporter = SpanExporter::default();let tracer_provider = TracerProvider::builder().with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio).build();
使用 tracing 集成
use tracing_subscriber::fmt;use tracing_opentelemetry::OpenTelemetryLayer;tracing_subscriber::registry().with(fmt::layer()).with(OpenTelemetryLayer::new(tracer)).init();
性能优化建议
- 使用批量导出: 默认 BatchSpanProcessor 已经启用
- 调整批量大小: 根据数据量调整
max_export_batch_size - 异步导出: 使用 Tokio 运行时进行异步导出
- 采样: 在生产环境使用较低的采样率
- 资源检测器: 只启用需要的资源检测器
下一步
- 环境变量配置 - 了解所有配置选项
- 高级配置 - 深入了解采样和性能优化
- Uptrace 平台使用 - 了解如何使用 Uptrace 平台