如何在Rust tracing/test-log单元测试中临时提升日志级别
问题
我在为使用tracing crate实现日志、test-log crate启用测试日志的Rust程序编写测试时遇到了需求冲突:
- 准备步骤操作较多,希望日志级别为
info; - 执行步骤需要
debug级别的日志以获取有效测试输出,但仅在该步骤生效。
我尝试了两种方法,但都有问题:
- 使用
reload Layer全局修改日志级别:虽然生效,但会影响全局日志配置; - 使用
with_default切换订阅者:同步版本在test-log中可行,但异步版本无法正常输出debug日志。
尝试的代码示例:
// 全局修改生效的代码 #[tokio::test] async fn test_dynamic_logging1() { use tracing_subscriber::{ filter, fmt, reload, prelude::* }; let filter = filter::LevelFilter::INFO; let (filter, reload_handle) = reload::Layer::new(filter); tracing_subscriber::registry().with(filter).with(fmt::Layer::default()).init(); info!("This will be logged"); debug!("This will be ignored"); reload_handle .modify(|filter| { *filter = filter::LevelFilter::DEBUG; }) .unwrap(); debug!("This will be logged"); } // 同步版本可行但异步失效的test-log代码 #[test_log::test(tokio::test)] async fn test_dynamic_logging3() { debug!("This will be ignored"); let debug_subscriber = tracing_subscriber::fmt::Subscriber ::builder() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .finish(); // 同步闭包有效 tracing::subscriber::with_default(debug_subscriber, || { debug!("This will be logged"); }); // 异步闭包失效 // tracing::subscriber::with_default(debug_subscriber, || async { // debug!("This will be logged"); // }).await; debug!("This will be ignored"); }
解决方案
要解决异步场景下with_default失效的问题,核心是使用异步版本的订阅者切换API,并正确结合test-log的配置:
1. 使用tracing::subscriber::with_default_async
tracing提供了异步版本的with_default_async函数,专门适配异步闭包场景。需要注意:
- 该函数要求订阅者实现
Clone(异步任务可能被调度到不同线程),创建订阅者时需确保支持克隆; - 配合
test-log时,订阅者必须使用with_test_writer()适配测试环境输出。
2. 修复后的代码示例
#[test_log::test(tokio::test)] async fn test_dynamic_logging_fixed() { debug!("This will be ignored (global info level)"); // 创建可克隆的debug级订阅者 let debug_subscriber = tracing_subscriber::fmt::Subscriber ::builder() .with_max_level(tracing::Level::DEBUG) .with_test_writer() .finish() .boxed(); // 装箱满足类型要求,也可用Arc包裹 // 异步闭包内切换订阅者 tracing::subscriber::with_default_async(debug_subscriber, async { debug!("This will be logged (debug level in async scope)"); // 此处执行需要debug日志的异步操作 info!("Info log also works here"); }).await; debug!("This will be ignored again (back to global info level)"); }
3. 备选方案:用Span实现局部日志级别控制
如果不想切换整个订阅者,可通过tracing的Span配合局部过滤实现需求:
#[test_log::test(tokio::test)] async fn test_local_debug_log() { info!("Preparation step (info level)"); debug!("Preparation debug will be ignored"); // 创建带debug级过滤的Span let debug_span = tracing::span!( tracing::Level::DEBUG, "execution_step" ); let _enter = debug_span.enter(); // Span范围内的debug日志会被输出 debug!("Execution step debug log"); info!("Execution step info log"); // 退出Span后恢复原级别 drop(_enter); debug!("Post-execution debug will be ignored"); }
关键注意事项
- 确保
tracing和tracing-subscriber使用最新稳定版,with_default_async是较新的API; - 全局默认日志级别可通过环境变量
TEST_LOG=info设置,匹配准备步骤需求; - 异步测试中避免全局修改日志级别,防止测试间的配置污染。
内容的提问来源于stack exchange,提问作者Niels Reijers
相关产品推荐
相关产品推荐

