Rust OTEL Hello-World无法向OTEL Collector上报Span问题排查
Rust应用OTEL追踪到Jaeger的配置与代码修复
问题背景
需要为Rust应用实现分布式追踪,通过OTEL Collector将数据导出到Jaeger 2.x版本查看。当前核心问题:
- Rust代码可正常编译,但运行至末尾会挂起
- 已验证OTEL Collector的HTTP/gRPC端点、Collector与Jaeger的联动均正常,Span已成功创建
可能诱因:
- OTEL与Jaeger官方已拆分,Jaeger 2.x all-in-one镜像对OTEL的支持逻辑发生变化
- OTEL API迭代频繁,旧代码示例因API弃用失效,多数公开示例侧重产品演示而非生产/开源部署配置
注:使用Orbstack作为Docker引擎,orb.local等价于localhost
已完成验证
- 通过curl发送
span.json验证OTEL Collector的HTTP端点正常 - 验证OTEL Collector与Jaeger的导出联动关系正常
- Rust代码可编译,但运行至末尾挂起
- 确认代码已成功创建Span
- 通过grpcurl验证OTEL Collector的gRPC端点正常
相关代码与配置
main.rs
use std::error::Error; use opentelemetry_sdk::{ trace::TracerProvider, runtime::Tokio, propagation::TraceContextPropagator, Resource }; use opentelemetry::{ global, trace::{TraceError, TraceContextExt, Tracer, Span, TracerProvider as _}, KeyValue }; use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig, ExportConfig}; use tokio::time::Duration; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {; global::set_text_map_propagator(TraceContextPropagator::new()); let mut exporter_config = opentelemetry_otlp::ExportConfig { protocol: opentelemetry_otlp::Protocol::Grpc, endpoint: Some("http://localhost:4317".to_string()), timeout: Duration::new(1, 0) }; let exporter = opentelemetry_otlp::SpanExporter::builder() .with_tonic() .with_export_config(exporter_config) .build()?; let provider = TracerProvider::builder() .with_batch_exporter(exporter, Tokio) .build(); global::set_tracer_provider(provider.clone()); let tracer = global::tracer("test-tracer"); tracer.in_span("my_test_span", |cx| { let span = cx.span(); let a = 1 + 1; span.set_attribute(KeyValue::new("test_key", a)); println!("Span attributes: {:?}", span); println!("Span Recording?: {:?}", span.is_recording()); }); provider.shutdown()?; Ok(()) }
Cargo.toml
[dependencies] dotenv = "0.15.0" opentelemetry = { version = "0.27.1", features = ["default"] } opentelemetry-otlp = "0.27.0" opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] } rand = "0.8.5" tokio = "1.43.0" tonic = "0.12.3"
docker-compose.yaml
运行说明:将相关配置文件放入新目录,执行
docker compose up -d启动。
services: otel-collector: image: otel/opentelemetry-collector command: ["--config=/etc/otelcol-contrib/config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml ports: - 1888:1888 - 8888:8888 - 8889:8889 - 13133:13133 - 4317:4317 - 4318:4318 - 55679:55679 depends_on: - jaeger-all-in-one - prometheus jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16686:16686" - "14250:14250" - "14317:14317" - "14318:14318" environment: COLLECTOR_OTLP_ENABLED: "true" COLLECTOR_OTLP_GRPC_HOST_PORT: 0.0.0.0:14317 COLLECTOR_OTLP_HTTP_HOST_PORT: 0.0.0.0:14318 prometheus: image: prom/prometheus:latest volumes: - ./prometheus.yaml:/etc/prometheus/prometheus.yml ports: - "9090:9090"
otel-collector-config.yaml
receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: batch: exporters: otlp: endpoint: otelcol:4317 otlp/jaeger: endpoint: jaeger-all-in-one:14317 tls: insecure: true prometheus: endpoint: "0.0.0.0:9090" namespace: default const_labels: label1: value1 debug: verbosity: detailed otlphttp: endpoint: "http://jaeger-all-in-one:14318" zipkin: endpoint: "http://localhost:9411/api/v2/spans" extensions: health_check: pprof: zpages: service: extensions: [health_check, pprof, zpages] pipelines: traces: receivers: [otlp] processors: [batch] exporters: [debug, otlp/jaeger] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] logs: receivers: [otlp] processors: [batch] exporters: [debug]
prometheus.yaml
global: scrape_interval: 5s scrape_configs: - job_name: 'otel-collector' static_configs: - targets: ['otel-collector:9090']
span.json
使用命令
curl -i http://localhost:4318/v1/traces -X POST -H "Content-Type: application/json" -d @span.json发送数据。
{ "resource_spans": [ { "resource": { "attributes": [ { "key": "cx.application.name", "value": { "string_value": "my-test-application" } }, { "key": "cx.subsystem.name", "value": { "string_value": "my-test-subsystem" } }, { "key": "service.name", "value": { "string_value": "my-test-service" } } ] }, "scope_spans": [ { "scope": { "name": "test" }, "spans": [ { "trace_id": "d0XALf50O6rdjlMxreTJsA==", "span_id": "YolJ2eFjJCs=", "name": "test-span", "kind": "SPAN_KIND_INTERNAL", "start_time_unix_nano": "1737649727", "end_time_unix_nano": "1737649779", "status": {} } ] } ] } ] }
grpcurl测试命令
git clone https://github.com/open-telemetry/opentelemetry-proto.git grpcurl -v -d @ \ -plaintext \ -proto opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service.proto \ -import-path opentelemetry-proto \ localhost:4317 \ opentelemetry.proto.collector.trace.v1.TraceService/Export \ < grpc.json
初始未加
-plaintext参数时报错,添加后请求成功,响应显示正常接收。
问题修复方案
1. Rust代码挂起的核心修复
代码挂起是因为provider.shutdown()是同步阻塞调用,在Tokio单线程runtime环境下会卡死事件循环。OTEL SDK 0.27版本提供了异步版本shutdown_async(),需替换为异步调用并await:
修复后的完整main.rs:
use std::error::Error; use opentelemetry_sdk::{ trace::TracerProvider, runtime::Tokio, propagation::TraceContextPropagator, Resource }; use opentelemetry::{ global, trace::{TraceContextExt, Tracer, Span, TracerProvider as _}, KeyValue }; use opentelemetry_otlp::{SpanExporter, WithExportConfig, ExportConfig}; use tokio::time::Duration; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> { global::set_text_map_propagator(TraceContextPropagator::new()); let exporter_config = opentelemetry_otlp::ExportConfig { protocol: opentelemetry_otlp::Protocol::Grpc, endpoint: Some("http://localhost:4317".to_string()), timeout: Duration::new(1, 0) }; let exporter = opentelemetry_otlp::SpanExporter::builder() .with_tonic() .with_export_config(exporter_config) .build()?; let provider = TracerProvider::builder() .with_batch_exporter(exporter, Tokio) .with_resource(Resource::new(vec![KeyValue::new("service.name", "rust-test-service")])) // 添加服务名称便于Jaeger识别 .build(); global::set_tracer_provider(provider.clone()); let tracer = global::tracer("test-tracer"); tracer.in_span("my_test_span", |cx| { let span = cx.span(); let a = 1 + 1; span.set_attribute(KeyValue::new("test_key", a)); println!("Span attributes: {:?}", span); println!("Span Recording?: {:?}", span.is_recording()); }); // 替换同步shutdown为异步版本 provider.shutdown_async().await?; Ok(()) }
2. Cargo依赖版本对齐
当前opentelemetry-otlp版本与核心库版本不一致,可能导致隐性兼容性问题,统一版本:
opentelemetry-otlp = "0.27.1"
3. OTEL Collector配置优化
- 移除无用的
otlpexporter(会导致循环发送数据) - 简化后的
exporters部分:
exporters: otlp/jaeger: endpoint: jaeger-all-in-one:14317 tls: insecure: true prometheus: endpoint: "0.0.0.0:9090" namespace: default const_labels: label1: value1 debug: verbosity: detailed
4. Jaeger容器配置简化
Jaeger 2.x all-in-one镜像默认已启用OTLP支持,无需额外环境变量:
jaeger-all-in-one: image: jaegertracing/all-in-one:latest ports: - "16686:16686" # UI端口 - "14317:14317" # OTLP gRPC端口 - "14318:14318" # OTLP HTTP端口
验证步骤
- 重启Docker容器:
docker compose down && docker compose up -d - 运行修复后的Rust代码:
cargo run - 访问Jaeger UI:打开
http://orb.local:16686,选择服务rust-test-service,点击"Find Traces"即可查看生成的my_test_span
内容的提问来源于stack exchange,提问作者aRustyDev
相关产品推荐
相关产品推荐

