Files
qiming/qiming-mcp-proxy/mcp-common/src/telemetry.rs
2026-06-01 13:03:20 +08:00

190 lines
5.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! OpenTelemetry 追踪模块
//!
//! 提供统一的分布式追踪初始化接口,支持 OTLP (Jaeger) exporter。
//!
//! # Feature Flags
//! - `telemetry`: 基础 OpenTelemetry 支持
//! - `otlp`: OTLP exporter 支持(用于 Jaeger
//!
//! # 使用示例
//!
//! ```ignore
//! use mcp_common::{TracingConfig, init_tracing};
//!
//! let config = TracingConfig::new("my-service")
//! .with_otlp("http://localhost:4317")
//! .with_version("1.0.0");
//!
//! let _guard = init_tracing(&config)?;
//! // guard 保持存活期间,追踪数据会被发送到 OTLP endpoint
//! ```
use anyhow::Result;
/// 追踪配置
#[derive(Debug, Clone, Default)]
pub struct TracingConfig {
/// 服务名称
pub service_name: String,
/// 服务版本
pub service_version: Option<String>,
/// OTLP 端点 (如 http://localhost:4317)
pub otlp_endpoint: Option<String>,
/// 采样率 (0.0 - 1.0),默认为 1.0(全部采样)
pub sample_ratio: Option<f64>,
}
impl TracingConfig {
/// 创建新的追踪配置
pub fn new(service_name: impl Into<String>) -> Self {
Self {
service_name: service_name.into(),
..Default::default()
}
}
/// 设置 OTLP 端点
pub fn with_otlp(mut self, endpoint: impl Into<String>) -> Self {
self.otlp_endpoint = Some(endpoint.into());
self
}
/// 设置服务版本
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.service_version = Some(version.into());
self
}
/// 设置采样率
pub fn with_sample_ratio(mut self, ratio: f64) -> Self {
self.sample_ratio = Some(ratio.clamp(0.0, 1.0));
self
}
}
/// 初始化追踪系统(启用 OTLP feature 时)
#[cfg(feature = "otlp")]
pub fn init_tracing(config: &TracingConfig) -> Result<TracingGuard> {
use opentelemetry::KeyValue;
use opentelemetry::global;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider};
let mut provider_builder = SdkTracerProvider::builder();
// 配置采样率
if let Some(ratio) = config.sample_ratio {
provider_builder = provider_builder.with_sampler(Sampler::TraceIdRatioBased(ratio));
}
// 配置 OTLP exporter
if let Some(endpoint) = &config.otlp_endpoint {
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.build()?;
provider_builder = provider_builder.with_batch_exporter(exporter);
tracing::info!(endpoint = %endpoint, "OTLP exporter configured");
}
// 配置资源属性
let mut attributes = vec![KeyValue::new("service.name", config.service_name.clone())];
if let Some(version) = &config.service_version {
attributes.push(KeyValue::new("service.version", version.clone()));
}
let resource = Resource::builder().with_attributes(attributes).build();
provider_builder = provider_builder.with_resource(resource);
let provider = provider_builder.build();
global::set_tracer_provider(provider.clone());
tracing::info!(
service_name = %config.service_name,
"OpenTelemetry tracer provider initialized"
);
Ok(TracingGuard {
provider: Some(provider),
})
}
/// 无 OTLP 时的空实现
#[cfg(not(feature = "otlp"))]
pub fn init_tracing(_config: &TracingConfig) -> Result<TracingGuard> {
tracing::debug!("OTLP feature not enabled, skipping tracer initialization");
Ok(TracingGuard { provider: None })
}
/// 创建 tracing-opentelemetry layer
///
/// 此 layer 可以添加到 tracing_subscriber 中,将 tracing 的 span 和事件
/// 转发到 OpenTelemetry。
#[cfg(feature = "telemetry")]
pub fn create_otel_layer() -> impl tracing_subscriber::Layer<tracing_subscriber::Registry> {
tracing_opentelemetry::layer()
}
/// 追踪守卫 - Drop 时自动关闭 tracer provider
///
/// 必须保持此守卫存活,否则追踪数据可能不会被正确发送。
pub struct TracingGuard {
#[cfg(feature = "otlp")]
provider: Option<opentelemetry_sdk::trace::SdkTracerProvider>,
#[cfg(not(feature = "otlp"))]
#[allow(dead_code)]
provider: Option<()>,
}
impl TracingGuard {
/// 检查追踪是否已初始化
pub fn is_initialized(&self) -> bool {
self.provider.is_some()
}
}
impl Drop for TracingGuard {
fn drop(&mut self) {
#[cfg(feature = "otlp")]
if let Some(provider) = self.provider.take() {
tracing::info!("Shutting down OpenTelemetry tracer provider");
if let Err(e) = provider.shutdown() {
tracing::warn!("Failed to shutdown tracer provider: {}", e);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracing_config_builder() {
let config = TracingConfig::new("test-service")
.with_otlp("http://localhost:4317")
.with_version("1.0.0")
.with_sample_ratio(0.5);
assert_eq!(config.service_name, "test-service");
assert_eq!(
config.otlp_endpoint,
Some("http://localhost:4317".to_string())
);
assert_eq!(config.service_version, Some("1.0.0".to_string()));
assert_eq!(config.sample_ratio, Some(0.5));
}
#[test]
fn test_sample_ratio_clamping() {
let config = TracingConfig::new("test").with_sample_ratio(1.5);
assert_eq!(config.sample_ratio, Some(1.0));
let config = TracingConfig::new("test").with_sample_ratio(-0.5);
assert_eq!(config.sample_ratio, Some(0.0));
}
}