You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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配置优化

  • 移除无用的otlp exporter(会导致循环发送数据)
  • 简化后的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端口

验证步骤

  1. 重启Docker容器:docker compose down && docker compose up -d
  2. 运行修复后的Rust代码:cargo run
  3. 访问Jaeger UI:打开http://orb.local:16686,选择服务rust-test-service,点击"Find Traces"即可查看生成的my_test_span

内容的提问来源于stack exchange,提问作者aRustyDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:52:02