添加qiming-rcoder模块

This commit is contained in:
Codex
2026-06-01 13:54:52 +08:00
parent 8092c4b1f8
commit 4b1a580132
539 changed files with 151650 additions and 0 deletions

View File

@@ -0,0 +1,352 @@
//! RCoder 遥测模块
//!
//! 提供统一的遥测功能,包括:
//! - **OTLP Tracing**: 分布式追踪,支持 Jaeger/OTLP Collector
//! - **Prometheus Metrics**: HTTP/gRPC 请求指标、业务指标
//! - **Trace Propagation**: 跨服务 trace context 传播
//! - **Console & File Logging**: 控制台和文件日志输出
//!
//! # 快速开始
//!
//! ```no_run
//! use rcoder_telemetry::{TelemetryConfig, init};
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//! // 从环境变量初始化配置
//! let config = TelemetryConfig::from_env("my-service");
//!
//! // Initializing telemetry system包含 console 日志、OTLP 追踪、Prometheus 指标)
//! let telemetry = init(config).await?;
//!
//! // 在应用中使用 telemetry.render_metrics() 暴露 /metrics 端点
//!
//! Ok(())
//! }
//! ```
//!
//! # 环境变量
//!
//! | 变量 | 说明 | 默认值 |
//! |-----|------|-------|
//! | `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP 端点 | - |
//! | `OTEL_SERVICE_NAME` | 服务名称 | 代码指定 |
//! | `OTEL_TRACES_SAMPLER_ARG` | 采样率 | `1.0` |
//! | `OTEL_EXPORTER_OTLP_PROTOCOL` | 协议 (grpc/http) | `grpc` |
//! | `TELEMETRY_PROMETHEUS_ENABLED` | 启用 Prometheus | `true` |
//! | `RUST_LOG` | 日志级别过滤 | `info` |
pub mod config;
pub mod middleware;
pub mod otlp;
pub mod prometheus;
pub mod propagation;
// Re-exports
pub use config::{FileLogConfig, OtlpConfig, PrometheusConfig, TelemetryConfig};
pub use middleware::{GrpcMetricsInterceptor, HttpMetricsLayer};
pub use prometheus::{
dec_active_tasks, inc_active_tasks, record_agent_task, record_agent_task_duration,
record_grpc_duration, record_grpc_request, record_http_duration, record_http_request,
set_active_tasks,
};
pub use propagation::{
extract_context, extract_context_http, inject_context, inject_context_http,
set_global_propagator,
};
use anyhow::Result;
use metrics_exporter_prometheus::PrometheusHandle;
use opentelemetry_sdk::trace::SdkTracerProvider;
use tracing::info;
use tracing_appender::rolling::Rotation;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
/// 遥测系统 Guard
///
/// 持有遥测资源的生命周期Drop 时自动清理。
/// 同时提供 Prometheus 指标渲染功能。
pub struct TelemetryGuard {
/// OTLP TracerProvider可选
tracer_provider: Option<SdkTracerProvider>,
/// Prometheus Handle可选
prometheus_handle: Option<PrometheusHandle>,
/// 服务名称
service_name: String,
}
impl TelemetryGuard {
/// 渲染 Prometheus 指标
///
/// 返回 Prometheus 文本格式的指标数据,
/// 可直接作为 `/metrics` 端点的响应。
///
/// # Returns
///
/// 如果 Prometheus 已启用,返回 `Some(metrics_text)`
/// 否则返回 `None`。
pub fn render_metrics(&self) -> Option<String> {
self.prometheus_handle.as_ref().map(|h| h.render())
}
/// 检查 OTLP 是否已启用
pub fn is_otlp_enabled(&self) -> bool {
self.tracer_provider.is_some()
}
/// 检查 Prometheus 是否已启用
pub fn is_prometheus_enabled(&self) -> bool {
self.prometheus_handle.is_some()
}
/// 获取服务名称
pub fn service_name(&self) -> &str {
&self.service_name
}
}
impl Drop for TelemetryGuard {
fn drop(&mut self) {
if self.tracer_provider.is_some() {
otlp::shutdown_tracer_provider();
}
info!(
"[Telemetry] Telemetry system shutdown: {}",
self.service_name
);
}
}
/// 一键Initializing telemetry system
///
/// 根据配置初始化完整的遥测栈:
/// - **Console 日志**: 始终启用,输出到标准输出
/// - **OTLP Tracing**: 如果配置了 OTLP 端点,将 span 导出到 Jaeger/Collector
/// - **Prometheus Metrics**: 如果启用,提供 `/metrics` 端点数据
///
/// # Arguments
///
/// * `config` - 遥测配置
///
/// # Returns
///
/// 返回 `TelemetryGuard`,持有遥测资源的生命周期。
///
/// # Example
///
/// ```no_run
/// use rcoder_telemetry::{TelemetryConfig, init};
///
/// #[tokio::main]
/// async fn main() -> anyhow::Result<()> {
/// let config = TelemetryConfig::new("my-service")
/// .with_otlp_endpoint("http://jaeger:4317")
/// .with_prometheus();
///
/// let telemetry = init(config).await?;
///
/// // 应用逻辑...
///
/// Ok(())
/// }
/// ```
pub async fn init(config: TelemetryConfig) -> Result<TelemetryGuard> {
// 设置全局传播器(在初始化 subscriber 之前)
set_global_propagator();
// 初始化 OTLP如果配置了
let tracer_provider = if let Some(ref otlp_config) = config.otlp {
let provider = otlp::init_tracer_provider(otlp_config, &config.service_name).await?;
otlp::set_global_tracer_provider(provider.clone());
Some(provider)
} else {
None
};
// Initializing Prometheus如果配置了
let prometheus_handle = if config.prometheus.is_some() {
Some(prometheus::init_prometheus()?)
} else {
None
};
// 🆕 初始化 tracing subscriber包括控制台、文件、OpenTelemetry
init_tracing_subscriber(
&config.service_name,
tracer_provider.as_ref(),
config.file_log.as_ref(),
)?;
info!(
"[Telemetry] Initializing telemetry system: {}",
config.service_name
);
info!(
"✅ [Telemetry] Telemetry system initialization completed: OTLP={}, Prometheus={}, FileLog={}, Console=true",
tracer_provider.is_some(),
prometheus_handle.is_some(),
config.file_log.is_some()
);
Ok(TelemetryGuard {
tracer_provider,
prometheus_handle,
service_name: config.service_name,
})
}
/// 初始化 tracing subscriber
///
/// 配置以下层:
/// - EnvFilter: 基于 RUST_LOG 环境变量的日志级别过滤
/// - Console Layer: 控制台日志输出
/// - File Layer: 可选的文件日志输出JSON 格式,按天滚动)
/// - OpenTelemetry Layer: 如果提供了 TracerProvider将 span 发送到 OTLP
fn init_tracing_subscriber(
service_name: &str,
tracer_provider: Option<&SdkTracerProvider>,
file_log_config: Option<&config::FileLogConfig>,
) -> Result<()> {
use opentelemetry::trace::TracerProvider;
// 创建 EnvFilter支持 RUST_LOG 环境变量)
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
// 默认日志级别
format!(
"{}=debug,tower_http=debug,axum=info,hyper=info,tonic=info",
service_name.replace('-', "_")
)
.into()
});
// 创建控制台日志层
let console_layer = fmt::layer()
.with_target(true)
.with_ansi(true)
.with_thread_ids(false)
.with_file(false)
.with_line_number(false);
// 创建文件日志层(如果配置了)
let file_layer = if let Some(file_config) = file_log_config {
// 创建日志目录
if !file_config.directory.exists() {
std::fs::create_dir_all(&file_config.directory)?;
}
// 创建按天滚动的 appender
let file_appender = tracing_appender::rolling::Builder::new()
.rotation(Rotation::DAILY)
.filename_prefix(&file_config.filename_prefix)
.max_log_files(file_config.max_log_files)
.build(&file_config.directory)?;
if file_config.json_format {
// JSON 格式文件日志
Some(
fmt::layer()
.json()
.with_writer(file_appender)
.with_ansi(false)
.with_target(true)
.with_thread_ids(true)
.with_thread_names(true)
.boxed(),
)
} else {
// 纯文本格式文件日志
Some(
fmt::layer()
.with_writer(file_appender)
.with_ansi(false)
.with_target(true)
.boxed(),
)
}
} else {
None
};
// 根据是否有 TracerProvider 和 FileLayer 决定如何初始化
match (tracer_provider, file_layer) {
(Some(provider), Some(file)) => {
// 有 OTLP + 文件日志
let tracer = provider.tracer(service_name.to_string());
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
tracing_subscriber::registry()
.with(env_filter)
.with(console_layer)
.with(file)
.with(otel_layer)
.init();
}
(Some(provider), None) => {
// 只有 OTLP
let tracer = provider.tracer(service_name.to_string());
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
tracing_subscriber::registry()
.with(env_filter)
.with(console_layer)
.with(otel_layer)
.init();
}
(None, Some(file)) => {
// 只有文件日志
tracing_subscriber::registry()
.with(env_filter)
.with(console_layer)
.with(file)
.init();
}
(None, None) => {
// 仅控制台
tracing_subscriber::registry()
.with(env_filter)
.with(console_layer)
.init();
}
}
Ok(())
}
/// 仅Initializing Prometheus不初始化 OTLP 和 tracing
///
/// 适用于只需要 metrics 不需要 tracing 的场景。
/// **注意**:此函数不会初始化 tracing subscriber调用方需要自行初始化。
pub fn init_prometheus_only(service_name: impl Into<String>) -> Result<TelemetryGuard> {
let service_name = service_name.into();
info!("[Telemetry] Initializing Prometheus: {}", service_name);
let prometheus_handle = prometheus::init_prometheus()?;
Ok(TelemetryGuard {
tracer_provider: None,
prometheus_handle: Some(prometheus_handle),
service_name,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_from_env() {
let config = TelemetryConfig::from_env("test-service");
assert_eq!(config.service_name, "test-service");
}
#[test]
fn test_config_builder() {
let config = TelemetryConfig::new("my-service")
.with_otlp_endpoint("http://localhost:4317")
.with_prometheus();
assert_eq!(config.service_name, "my-service");
assert!(config.otlp.is_some());
assert!(config.prometheus.is_some());
}
}