如何使用Rust tracing_subscriber根据Span时长过滤慢Span?
如何使用Rust tracing_subscriber根据Span时长过滤慢Span?
首先要明确一个关键点:你用的FilterFn没办法实现根据Span时长过滤,因为Filter的执行时机是在Span创建、事件触发之前,这时候Span还没结束,根本没有时长数据可以获取。要实现这个需求,你需要用自定义Layer——Layer可以监听Span的全生命周期事件(比如开始、关闭),在Span关闭时计算时长,再决定是否记录这个慢Span。
结合你的现有代码,我给你整理了完整的修改方案:
第一步:添加必要的依赖导入
首先在你的代码顶部补充这些导入:
use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use tracing::{Id, Metadata}; use tracing_subscriber::{ layer::{Context, Layer}, registry::LookupSpan, fmt, EnvFilter, FilterFn, };
第二步:实现自定义慢Span跟踪Layer
这个Layer会帮你记录每个Span的开始时间,在Span关闭时计算时长,把慢Span标记出来:
// 自定义Layer:跟踪Span开始时间,标记时长超过阈值的慢Span #[derive(Debug, Clone)] struct SlowSpanLayer { min_slow_duration: Duration, span_start_times: Arc<Mutex<HashMap<Id, Instant>>>, } impl SlowSpanLayer { // 初始化:传入慢Span的时长阈值,比如100ms fn new(min_slow_duration: Duration) -> Self { Self { min_slow_duration, span_start_times: Arc::new(Mutex::new(HashMap::new())), } } } // 实现Layer trait,监听Span生命周期 impl<S> Layer<S> for SlowSpanLayer where S: tracing::Subscriber + for<'a> LookupSpan<'a>, { // Span创建时,记录开始时间 fn on_new_span(&self, _attrs: &tracing::span::Attributes<'_>, id: &Id, ctx: Context<'_, S>) { let span = ctx.span(id).expect("找不到对应的Span"); // 这里可以加过滤,只跟踪你自己的应用Span(比如target是mubbles的) if span.metadata().target().starts_with("mubbles") { let start_time = Instant::now(); self.span_start_times.lock().unwrap().insert(id.clone(), start_time); } } // Span关闭时,计算时长,标记慢Span fn on_close(&self, id: Id, ctx: Context<'_, S>) { let span = ctx.span(&id).expect("找不到对应的Span"); let mut start_times = self.span_start_times.lock().unwrap(); if let Some(start_time) = start_times.remove(&id) { let duration = start_time.elapsed(); // 如果时长超过阈值,把时长存入Span的扩展中,作为慢Span的标记 if duration >= self.min_slow_duration { span.extensions_mut().insert(duration); } } } }
第三步:修改你的set_up_tracing函数
把自定义Layer整合到你的现有代码中,同时修改文件层的过滤逻辑:
fn set_up_tracing() -> Box<dyn std::any::Any> { // 日志文件滚动配置:保留3个文件,每日滚动,单个文件最大10MB let file_appender = rolling_file::BasicRollingFileAppender::new( "./mubbles.log", rolling_file::RollingConditionBasic::new() .daily() .max_size(1024 * 1024 * 10), 3, ) .expect("无法打开日志文件"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); // 过滤函数:只允许被标记为慢Span的关闭事件通过 let slow_span_filter = FilterFn::new(|_| { // 检查当前Span是否被标记为慢Span(即扩展中存有时长数据) tracing::Span::current().extensions().get::<Duration>().is_some() }); // 控制台输出层:只输出WARN及以上级别,保持你原来的配置 let console_layer = tracing_subscriber::fmt::Layer::new() .pretty() .with_writer(std::io::stdout) .with_max_level(tracing::Level::WARN) .with_filter(EnvFilter::from_default_env()); // 文件输出层:只记录慢Span的关闭事件,保留你原来的格式配置 let file_layer = tracing_subscriber::fmt::Layer::new() .with_writer(non_blocking) .with_ansi(false) .with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE) .with_filter(slow_span_filter) .with_filter(EnvFilter::from_default_env()); // 初始化自定义慢SpanLayer:这里设置阈值为100ms,你可以根据需求调整 let slow_span_layer = SlowSpanLayer::new(Duration::from_millis(100)); // 设置全局订阅者 tracing::subscriber::set_global_default( tracing_subscriber::registry() .with(console_layer) .with(slow_span_layer) // 先添加自定义层跟踪Span时长 .with(file_layer), // 再添加文件层记录慢Span ) .expect("无法设置全局tracing订阅者"); Box::new(_guard) }
关键逻辑说明
- 自定义Layer的作用:
- 每个Span创建时,记录它的开始时间;
- Span关闭时计算时长,超过阈值的Span会在自己的扩展中存入时长数据,作为"慢Span"的标记。
- 过滤函数的作用:
- 只允许带有慢标记的Span的关闭事件通过,这样文件层只会记录这些慢Span的时长日志。
- 线程安全:
- 用
Arc<Mutex<HashMap>>存储Span的开始时间,确保多线程环境下的安全性。
- 用
你可以根据自己的需求调整慢Span的阈值(比如把Duration::from_millis(100)改成500ms或者1s),如果需要跟踪特定模块的Span,也可以在on_new_span里修改过滤条件(比如检查metadata.name()或者metadata.module_path())。
内容来源于stack exchange
相关产品推荐
相关产品推荐

