配送服务

此服务负责在 Checkout Service 请求时,提供包括价格和跟踪信息在内的运输信息。

Shipping service 使用 Actix Web、用于日志的 Tracing 和 OpenTelemetry 库构建。所有其他子依赖项都包含在 Cargo.toml 中。

根据您的框架和运行时,您可能需要参考 Rust 文档 作为补充。您会发现异步和同步 Span 的示例分别用于报价请求和跟踪 ID。

Shipping service 源代码

仪表

OpenTelemetry SDK 在 telemetry_conf 文件中进行配置。

实现了一个 get_resource() 函数,使用默认的 Resource Detectors 以及 OSProcess 检测器来创建 Resource。

fn get_resource() -> Resource {
    let detectors: Vec<Box<dyn ResourceDetector>> = vec![
        Box::new(OsResourceDetector),
        Box::new(ProcessResourceDetector),
    ];

    Resource::builder().with_detectors(&detectors).build()
}

get_resource() 就位后,该函数可以在所有 Provider 初始化中多次调用。

初始化 Tracer Provider

fn init_tracer_provider() {
    global::set_text_map_propagator(TraceContextPropagator::new());

    let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
        .with_resource(get_resource())
        .with_batch_exporter(
            opentelemetry_otlp::SpanExporter::builder()
                .with_tonic()
                .build()
                .expect("Failed to initialize tracing provider"),
        )
        .build();

    global::set_tracer_provider(tracer_provider);
}

初始化 Meter Provider

fn init_meter_provider() -> opentelemetry_sdk::metrics::SdkMeterProvider {
    let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
        .with_resource(get_resource())
        .with_periodic_exporter(
            opentelemetry_otlp::MetricExporter::builder()
                .with_temporality(opentelemetry_sdk::metrics::Temporality::Delta)
                .with_tonic()
                .build()
                .expect("Failed to initialize metric exporter"),
        )
        .build();
    global::set_meter_provider(meter_provider.clone());

    meter_provider
}

初始化 Logger Provider

对于日志,Shipping service 使用 Tracing,因此使用 OpenTelemetryTracingBridge 将 Tracing crate 的日志桥接到 OpenTelemetry。

fn init_logger_provider() {
    let logger_provider = opentelemetry_sdk::logs::SdkLoggerProvider::builder()
        .with_resource(get_resource())
        .with_batch_exporter(
            opentelemetry_otlp::LogExporter::builder()
                .with_tonic()
                .build()
                .expect("Failed to initialize logger provider"),
        )
        .build();

    let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
    let filter_otel = EnvFilter::new("info");
    let otel_layer = otel_layer.with_filter(filter_otel);

    tracing_subscriber::registry().with(otel_layer).init();
}

初始化仪表

在定义了用于初始化 Traces、Metrics 和 Logs 的 Provider 的函数之后,创建了一个公共函数 init_otel()

pub fn init_otel() -> Result<()> {
    init_logger_provider();
    init_tracer_provider();
    init_meter_provider();
    Ok(())
}

如果一切正常启动,此函数将调用所有初始化程序并返回 OK(())

init_otel() 函数随后在 main 中调用。

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    match init_otel() {
        Ok(_) => {
            info!("Successfully configured OTel");
        }
        Err(err) => {
            panic!("Couldn't start OTel: {0}", err);
        }
    };

    [...]

}

仪表配置

在 Provider 配置和初始化之后,Shipping 使用 opentelemetry-instrumentation-actix-web crate 在服务器端和客户端配置期间对应用程序进行仪表。

服务器端

服务器包装了 RequestTracingRequestMetrics,以便在接收请求时自动创建 Traces 和 Metrics。

HttpServer::new(|| {
    App::new()
        .wrap(RequestTracing::new())
        .wrap(RequestMetrics::default())
        .service(get_quote)
        .service(ship_order)
})

客户端

在向另一个服务发出请求时,将 trace_request() 添加到调用中。

let mut response = client
    .post(quote_service_addr)
    .trace_request()
    .send_json(&reqbody)
    .await
    .map_err(|err| anyhow::anyhow!("Failed to call quote service: {err}"))?;

手动仪表

opentelemetry-instrumentation-actix-web crate 允许我们通过添加前一节中提到的命令来仪表服务器端和客户端。

在 Demo 中,我们还演示了如何手动增强自动创建的 Span 以及如何在应用程序上创建手动指标。

手动 Span

在以下代码片段中,当前活动的 Span 会通过 Span 事件和 Span 属性得到增强。

Ok(get_active_span(|span| {
    let q = create_quote_from_float(f);
    span.add_event(
        "Received Quote".to_string(),
        vec![KeyValue::new("app.shipping.cost.total", format!("{}", q))],
    );
    span.set_attribute(KeyValue::new("app.shipping.cost.total", format!("{}", q)));
    q
}))

手动指标

创建了一个自定义指标计数器来计算运输请求中有多少项。

let meter = global::meter("otel_demo.shipping.quote");
let counter = meter.u64_counter("app.shipping.items_count").build();
counter.add(count as u64, &[]);

日志

由于 Shipping service 使用 Tracing 作为日志接口,它使用 opentelemetry-appender-tracing crate 将 Tracing 日志桥接到 OpenTelemetry 日志。

初始化 logger provider 时,已经使用以下两行配置了 appender:

let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
tracing_subscriber::registry().with(otel_layer).init();

有了这些,我们就可以像平常一样使用 Tracing,例如:

info!(
    name = "SendingQuoteValue",
    quote.dollars = quote.dollars,
    quote.cents = quote.cents,
    message = "Sending Quote"
);

opentelemetry-appender-tracing crate 负责将 OpenTelemetry 上下文添加到日志条目中,最终导出的日志包含所有配置的资源属性和 TraceContext 信息。


最后修改时间 2025 年 6 月 10 日: [demo] Update Shipping service docs (#7062) (f92688f0)