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.rs
use 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-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
# OTLP Exporter(内部平台)
export OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export 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.rs
use 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.rs
mod 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 headers
let 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");
// 在异步任务中创建 span
let 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 headers
let 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;
// 创建多个 exporter
let 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();
// 创建多个 processor
let otlp_processor = opentelemetry_sdk::trace::BatchSpanProcessor::builder(
otlp_exporter,
opentelemetry_sdk::runtime::Tokio,
)
.build();
let console_processor = opentelemetry_sdk::trace::SimpleSpanProcessor::new(console_exporter);
// 创建 TracerProvider
let 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 传递(在异步环境中)

// ✅ 正确:在异步函数中传递 context
async 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-service
export OTEL_SERVICE_VERSION=1.0.0
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production
# OTLP Exporter(内部平台)
export OTEL_EXPORTER_OTLP_ENDPOINT=http://uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc/protobuf
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project?grpc=4317
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Metrics 配置
export OTEL_METRICS_EXPORTER=otlp
export OTEL_METRIC_EXPORT_INTERVAL=60000
# Logs 配置
export OTEL_LOGS_EXPORTER=otlp
export 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 宏自动创建 span
tracing::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. 数据未发送到后端

排查步骤:

  1. 检查环境变量:
echo $OTEL_SERVICE_NAME
echo $OTEL_EXPORTER_OTLP_ENDPOINT
  1. 检查 tracer provider 是否初始化:
let tracer = global::tracer("test");
// 如果 tracer provider 未初始化,会使用 NoOpTracer
  1. 检查网络连接:
// 测试端点是否可达
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 {
// 在新的异步任务中使用 context
let 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=debug
export 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();

性能优化建议

  1. 使用批量导出: 默认 BatchSpanProcessor 已经启用
  2. 调整批量大小: 根据数据量调整 max_export_batch_size
  3. 异步导出: 使用 Tokio 运行时进行异步导出
  4. 采样: 在生产环境使用较低的采样率
  5. 资源检测器: 只启用需要的资源检测器

下一步

参考资源

在Gitlab上编辑本文档