Files
qiming/qiming-rcoder/crates/rcoder-telemetry/src/propagation.rs
2026-06-01 13:54:52 +08:00

213 lines
5.8 KiB
Rust
Raw Permalink 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.
//! Trace Context 传播模块
//!
//! 提供跨服务的 trace context 传播功能,支持 gRPC 和 HTTP。
use opentelemetry::propagation::{Extractor, Injector, TextMapPropagator};
use opentelemetry::Context;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use tonic::metadata::{MetadataKey, MetadataMap, MetadataValue};
use tracing::debug;
/// gRPC MetadataMap 的 Injector 实现
struct MetadataMapInjector<'a>(&'a mut MetadataMap);
impl Injector for MetadataMapInjector<'_> {
fn set(&mut self, key: &str, value: String) {
if let Ok(key) = MetadataKey::from_bytes(key.as_bytes()) {
if let Ok(value) = MetadataValue::try_from(&value) {
self.0.insert(key, value);
}
}
}
}
/// gRPC MetadataMap 的 Extractor 实现
struct MetadataMapExtractor<'a>(&'a MetadataMap);
impl Extractor for MetadataMapExtractor<'_> {
fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).and_then(|value| value.to_str().ok())
}
fn keys(&self) -> Vec<&str> {
self.0
.keys()
.filter_map(|key| {
if let tonic::metadata::KeyRef::Ascii(k) = key {
Some(k.as_str())
} else {
None
}
})
.collect()
}
}
/// 注入 trace context 到 gRPC metadata
///
/// 将当前 span 的 trace context 注入到 gRPC metadata 中,
/// 用于跨服务传播。
///
/// # Arguments
///
/// * `metadata` - gRPC metadata
///
/// # Example
///
/// ```no_run
/// use tonic::metadata::MetadataMap;
/// use rcoder_telemetry::propagation::inject_context;
///
/// let mut metadata = MetadataMap::new();
/// inject_context(&mut metadata);
/// // 现在 metadata 包含 traceparent 和 tracestate headers
/// ```
pub fn inject_context(metadata: &mut MetadataMap) {
let propagator = TraceContextPropagator::new();
let cx = Context::current();
let mut injector = MetadataMapInjector(metadata);
propagator.inject_context(&cx, &mut injector);
debug!("[Propagation] Trace context injected into gRPC metadata");
}
/// Extracting trace context from gRPC metadata
///
/// 从 gRPC metadata 中提取 trace context
/// 用于继续跨服务的 trace。
///
/// # Arguments
///
/// * `metadata` - gRPC metadata
///
/// # Returns
///
/// 返回提取的 `Context`,如果没有找到则返回当前 context。
///
/// # Example
///
/// ```no_run
/// use tonic::metadata::MetadataMap;
/// use rcoder_telemetry::propagation::extract_context;
///
/// let metadata = MetadataMap::new();
/// let context = extract_context(&metadata);
/// // 使用 context 创建新的 span
/// ```
pub fn extract_context(metadata: &MetadataMap) -> Context {
let propagator = TraceContextPropagator::new();
let extractor = MetadataMapExtractor(metadata);
let cx = propagator.extract(&extractor);
debug!("[Propagation] Extracting trace context from gRPC metadata");
cx
}
/// HTTP Headers 的 Injector 实现
pub struct HttpHeaderInjector<'a>(pub &'a mut http::HeaderMap);
impl Injector for HttpHeaderInjector<'_> {
fn set(&mut self, key: &str, value: String) {
if let Ok(name) = http::header::HeaderName::from_bytes(key.as_bytes()) {
if let Ok(value) = http::header::HeaderValue::from_str(&value) {
self.0.insert(name, value);
}
}
}
}
/// HTTP Headers 的 Extractor 实现
pub struct HttpHeaderExtractor<'a>(pub &'a http::HeaderMap);
impl Extractor for HttpHeaderExtractor<'_> {
fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).and_then(|value| value.to_str().ok())
}
fn keys(&self) -> Vec<&str> {
self.0.keys().map(|key| key.as_str()).collect()
}
}
/// 注入 trace context 到 HTTP headers
///
/// # Arguments
///
/// * `headers` - HTTP headers
pub fn inject_context_http(headers: &mut http::HeaderMap) {
let propagator = TraceContextPropagator::new();
let cx = Context::current();
let mut injector = HttpHeaderInjector(headers);
propagator.inject_context(&cx, &mut injector);
debug!("[Propagation] Trace context injected into HTTP headers");
}
/// Extracting trace context from HTTP headers
///
/// # Arguments
///
/// * `headers` - HTTP headers
///
/// # Returns
///
/// 返回提取的 `Context`
pub fn extract_context_http(headers: &http::HeaderMap) -> Context {
let propagator = TraceContextPropagator::new();
let extractor = HttpHeaderExtractor(headers);
let cx = propagator.extract(&extractor);
debug!("[Propagation] Extracting trace context from HTTP headers");
cx
}
/// 设置全局 text map 传播器
///
/// 应该在应用启动时调用一次。
pub fn set_global_propagator() {
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
debug!("[Propagation] Global TraceContextPropagator set");
}
#[cfg(test)]
mod tests {
use super::*;
use opentelemetry::trace::TraceContextExt;
#[test]
fn test_metadata_injector_extractor() {
let mut metadata = MetadataMap::new();
// 手动设置一些 metadata
metadata.insert(
"traceparent",
"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
.parse()
.unwrap(),
);
// 提取 context
let cx = extract_context(&metadata);
assert!(!cx.span().span_context().trace_id().to_string().is_empty());
}
#[test]
fn test_http_header_injector_extractor() {
let mut headers = http::HeaderMap::new();
// 手动设置 traceparent header
headers.insert(
"traceparent",
"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
.parse()
.unwrap(),
);
// 提取 context
let cx = extract_context_http(&headers);
assert!(!cx.span().span_context().trace_id().to_string().is_empty());
}
}