用途
Maple 的 Rust OpenTelemetry 接入风格。目标是让 Rust 服务把 trace、log、metrics 通过 OTLP HTTP 协议发送到 Maple 的 ingest 端点。
主要能力
- 使用官方 opentelemetry + opentelemetry_sdk crate,配合 opentelemetry-otlp 导出(HTTP exporter,不是 gRPC)。
- 桥接 tracing crate:tracing-opentelemetry 把 tracing span 转成 OTel span;opentelemetry-appender-tracing 把 info! / error! 事件转成 OTLP log records。
- 同时导出 traces、logs、metrics 三类信号。
- 提供资源属性设置,包括 service name、deployment.environment.name、vcs.repository.url.full、vcs.ref.head.revision。
使用方式
依赖(Cargo.toml)
[dependencies]
opentelemetry = "0.32"
opentelemetry_sdk = { version = "0.32", features = ["trace", "logs", "metrics"] }
opentelemetry-otlp = { version = "0.32", features = ["http-proto", "reqwest-blocking-client", "reqwest-rustls", "trace", "logs", "metrics"] }
opentelemetry-appender-tracing = "0.32"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-opentelemetry = "0.33"
版本约束说明:
- opentelemetry* 系列 crate 保持同一 minor 版本。
- tracing-opentelemetry 需与为其构建的版本配对(0.32 对应 0.33)。
- builder 默认的 batch processors 从自己的线程导出,因此必须使用 blocking reqwest client;async reqwest-client 在该场景会 panic,报错 "there is no reactor running"。
- HTTPS 需要 reqwest-rustls:opentelemetry-otlp 构建 reqwest 时不带默认 TLS。
Bootstrap
端点与 ingest key 采用内联方式。key 是项目级、只写的 token(形如 Sentry DSN)。代码示例:
use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_otlp::{
ExporterBuildError, LogExporter, MetricExporter, Protocol, SpanExporter, WithExportConfig,
WithHttpConfig,
};
use opentelemetry_sdk::{
logs::SdkLoggerProvider, metrics::SdkMeterProvider, trace::SdkTracerProvider, Resource,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
const MAPLE_ENDPOINT: &str = "https://ingest.maple.dev"; // EU: https://ingest.eu.maple.dev
const MAPLE_KEY: &str = "MAPLE_TEST"; // public ingest key (maple_pk_…), or MAPLE_TEST until the user has one
pub fn init() -> Result<(SdkTracerProvider, SdkLoggerProvider, SdkMeterProvider), ExporterBuildError> {
let auth = format!("Bearer {MAPLE_KEY}");
let mut headers = std::collections::HashMap::new();
headers.insert("authorization".to_string(), auth);
let resource = Resource::builder()
.with_service_name("orders-api")
.with_attributes([
KeyValue::new("deployment.environment.name", std::env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "development".into())),
KeyValue::new("vcs.repository.url.full", "https://github.com/acme/orders-api"),
KeyValue::new("vcs.ref.head.revision", std::env::var("GITHUB_SHA").unwrap_or_default()),
])
.build();
let trace_exporter = SpanExporter::builder()
.with_http()
.with_endpoint(format!("{MAPLE_ENDPOINT}/v1/traces"))
.with_headers(headers.clone())
.with_protocol(Protocol::HttpBinary)
.build()?;
let tracer_provider = SdkTracerProvider::builder()
.with_batch_exporter(trace_exporter)
.with_resource(resource.clone())
.build();
global::set_tracer_provider(tracer_provider.clone());
let log_exporter = LogExporter::builder()
.with_http()
.with_endpoint(format!("{MAPLE_ENDPOINT}/v1/logs"))
.with_headers(headers.clone())
.with_protocol(Protocol::HttpBinary)
.build()?;
let logger_provider = SdkLoggerProvider::builder()
.with_batch_exporter(log_exporter)
.with_resource(resource.clone())
.build();
let metric_exporter = MetricExporter::builder()
.with_http()
.with_endpoint(format!("{MAPLE_ENDPOINT}/v1/metrics"))
.with_headers(headers)
.with_protocol(Protocol::HttpBinary)
.build()?;
let meter_provider = SdkMeterProvider::builder()
.with_periodic_exporter(metric_exporter)
.with_resource(resource)
.build();
global::set_meter_provider(meter_provider.clone());
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer_provider.tracer("orders.api"));
let otel_log_layer = OpenTelemetryTracingBridge::new(&logger_provider);
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer())
.with(otel_layer)
.with(otel_log_layer)
.init();
Ok((tracer_provider, logger_provider, meter_provider))
}
在 main 中调用并在退出时关闭:
#[tokio::main]
async fn main() {
// A telemetry setup error is logged, never fatal: the app runs without export.
let providers = telemetry::init()
.inspect_err(|err| eprintln!("telemetry disabled: {err}"))
.ok();
// app run …
if let Some((tracer_provider, logger_provider, meter_provider)) = providers {
let _ = tracer_provider.shutdown();
let _ = logger_provider.shutdown();
let _ = meter_provider.shutdown();
}
}
遥测初始化失败只记录日志,不视为致命错误:应用在没有导出的情况下继续运行。
通过 tracing 定义有界业务 span
桥接 tracing 可保持既有埋点不变。对有界异步操作使用 #[tracing::instrument]:
#[tracing::instrument(name = "order.submit", skip_all, err, fields(order.id = %order_id))]
async fn submit_order(order_id: &str) -> Result<(), Error> {
charge_order(order_id).await?;
Ok(())
}
在 #[instrument] 中加上 err。返回 Err 时会发出带 error 字段的事件,tracing-opentelemetry 会将其记录为 exception event 以及 span 状态 Error(二者默认开启)。
共存
如果项目已在使用 tracing 并接有 Honeycomb、Datadog 或 Jaeger 的 layer,保持其原样,把 Maple 的 layer 并行加上即可。除非用户要求,不要移除已有的导出器。
适用场景
- Rust 服务需要接入 Maple 的 trace、log、metrics 上报。
- 已有 tracing 埋点的项目,希望在不改动现有 instrumentation 的情况下增加 OTel 导出。
- 需要 OTLP HTTP(非 gRPC)导出并且要求 HTTPS 支持的场景。
限制
- 导出协议为 HTTP(Protocol::HttpBinary),非 gRPC。
- 必须使用 blocking reqwest client,async client 会在 batch 导出线程中 panic。
- HTTPS 必须启用 reqwest-rustls。
- opentelemetry* crate 需保持同一 minor 版本,tracing-opentelemetry 需配对其对应版本(0.32 对应 0.33)。
- 文档中除示例中的服务名、仓库地址与端点外,暂未提供其他平台、收费或价格信息。