You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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)
}

关键逻辑说明

  1. 自定义Layer的作用:
    • 每个Span创建时,记录它的开始时间;
    • Span关闭时计算时长,超过阈值的Span会在自己的扩展中存入时长数据,作为"慢Span"的标记。
  2. 过滤函数的作用:
    • 只允许带有慢标记的Span的关闭事件通过,这样文件层只会记录这些慢Span的时长日志。
  3. 线程安全:
    • 用Arc<Mutex<HashMap>>存储Span的开始时间,确保多线程环境下的安全性。

你可以根据自己的需求调整慢Span的阈值(比如把Duration::from_millis(100)改成500ms或者1s),如果需要跟踪特定模块的Span,也可以在on_new_span里修改过滤条件(比如检查metadata.name()或者metadata.module_path())。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:39:28