Unable to install global subscriber 与 failed to set global default subscriber
主线位置:事件主线开始之前。
init把 subscriber 装成全局默认,之后每条事件在get_default那一步找到的就是它;这一步失败,事件要么去了别人装的 subscriber,要么哪儿都没去。
flowchart TB init["init()"] --> tryinit["try_init()"] tryinit --> sgd["set_global_default"] sgd -->|已经有了| e1["SetGlobalDefaultError"] sgd -->|成功| lt["LogTracer init"] lt -->|log 已被占| e2["SetLoggerError"] lt -->|成功| ok["Ok"] e1 --> wrap["包进 TryInitError"] e2 --> wrap wrap -->|init| pan["expect 时 panic"] wrap -->|"let _ ="| lost["两种都看不到"]
init 和 try_init 做的两件事、set_global_default 为什么只能成功一次,在 fmt 的「SubscriberBuilder、init 与 try_init」和 Dispatch:全局与线程局部 里讲过。这一页看它们在真实程序里的样子:panic 长什么样、出现在哪、try_init 的错误到底是哪一种。
本页的实验都在 M2 Max、rustc 1.99.0、--release 下运行,依赖:
tracing = "=0.1.44"
tracing-subscriber = "=0.3.23"
log = "0.4"
tracing-subscriber 用的是默认 feature,其中包括 fmt 和 tracing-log;后者决定了下面第三种 panic 会不会出现。
复现:三种 panic
use tracing_subscriber::prelude::*;
fn main() {
let case = std::env::args().nth(1).unwrap_or_default();
match case.as_str() {
// SubscriberBuilder::init 两次
"builder" => {
tracing_subscriber::fmt().init();
tracing_subscriber::fmt().init();
}
// SubscriberInitExt::init 两次
"ext" => {
tracing_subscriber::registry().with(tracing_subscriber::fmt::layer()).init();
tracing_subscriber::registry().with(tracing_subscriber::fmt::layer()).init();
}
// 别人先占了 log 的 logger,再 init
"logger" => {
log::set_max_level(log::LevelFilter::Info);
let _ = log::set_boxed_logger(Box::new(Other));
tracing_subscriber::fmt().init();
}
_ => {}
}
println!("not reached");
}
struct Other;
impl log::Log for Other {
fn enabled(&self, _: &log::Metadata<'_>) -> bool {
true
}
fn log(&self, r: &log::Record<'_>) {
println!("[other] {}", r.args());
}
fn flush(&self) {}
}
三个参数各跑一次,stdout、stderr 合在一起,连跑 3 遍,除线程号外输出相同:
== builder
thread 'main' (1568791) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/fmt/mod.rs:520:14:
Unable to install global subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
== ext
thread 'main' (1568825) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/util.rs:94:14:
failed to set global default subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
== logger
thread 'main' (1568863) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/fmt/mod.rs:520:14:
Unable to install global subscriber: SetLoggerError(())
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit=101
- 前缀:
fmt().init()是Unable to install global subscriber,registry().with(..).init()是failed to set global default subscriber。同一个错误,两段文字,搜报错时两个都要搜。 - 冒号后面:
SetGlobalDefaultError(..)说的是全局默认已经有了;SetLoggerError(())说的是logcrate 的 logger 已经有了,和 tracing 的全局默认无关。 - 位置:panic 报在 tracing-subscriber 的
fmt/mod.rs:520或util.rs:94,不是你写init()的那一行。这两个init没有标#[track_caller]。
源码:两个 expect,一个两步的 try_init
两个 init 都只是 try_init 加 expect。SubscriberBuilder::init:
pub fn init(self) {
self.try_init()
.expect("Unable to install global subscriber")
}
fn init(self) {
self.try_init()
.expect("failed to set global default subscriber")
}
expect 打印的是 {msg}: {err:?},所以冒号后面是错误的 Debug。SubscriberBuilder::try_init 先 finish(),再交给 SubscriberInitExt::try_init:
fn try_init(self) -> Result<(), TryInitError> {
dispatcher::set_global_default(self.into()).map_err(TryInitError::new)?;
// ...
#[cfg(feature = "tracing-log")]
tracing_log::LogTracer::builder()
// Note that we must call this *after* setting the global default
// subscriber, so that we get its max level hint.
.with_max_level(tracing_core::LevelFilter::current().as_log())
.init()
.map_err(TryInitError::new)?;
Ok(())
}
两个 ?,两种错误:
| 哪一步失败 | 错误 | 全局 subscriber | log::info! 去哪 |
|---|---|---|---|
set_global_default | SetGlobalDefaultError | 没装上,还是之前那个 | 第二步没执行,log 没有 logger |
LogTracer 的 Builder::init | log::SetLoggerError | 已经装上 | 先装的那个 logger |
第二行就是第三种 panic 的情形:panic 的文字说「Unable to install global subscriber」,subscriber 其实已经装好了。在 init 外面套一个 catch_unwind 就能看到:
fn main() {
log::set_logger(&Other).unwrap();
log::set_max_level(log::LevelFilter::Info);
let r = std::panic::catch_unwind(|| {
tracing_subscriber::fmt().with_ansi(false).without_time().init();
});
println!("init panicked: {}", r.is_err());
println!("has_been_set: {}", tracing::dispatcher::has_been_set());
tracing::info!("tracing event after the panic");
}
(Other 同上。)连跑 3 次,除线程号外相同:
thread 'main' (1576874) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/fmt/mod.rs:520:14:
Unable to install global subscriber: SetLoggerError(())
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
init panicked: true
has_been_set: true
INFO init_logger_panic: tracing event after the panic
try_init 加 let _ =:分不清是哪一种
为了不 panic,很多代码写成 let _ = tracing_subscriber::fmt().try_init();。这样两种失败都悄无声息。就算不扔掉错误,想在代码里分辨是哪一种也做不到:
fn main() {
let case = std::env::args().nth(1).unwrap_or_default();
match case.as_str() {
// 别的代码先装了一个全局 subscriber(这里用一个什么都不收的)
"global-taken" => {
tracing::subscriber::set_global_default(tracing::subscriber::NoSubscriber::default()).unwrap();
}
// 别的代码先装了 log 的 logger
"logger-taken" => {
log::set_logger(&Other).unwrap();
log::set_max_level(log::LevelFilter::Info);
}
_ => {}
}
let res = tracing_subscriber::fmt().with_ansi(false).without_time().try_init();
match &res {
Ok(()) => println!("try_init -> Ok"),
Err(e) => {
println!("try_init -> Err, Display = {e}");
println!(" source() = {:?}", e.source());
println!(
" downcast to SetGlobalDefaultError: {}",
e.downcast_ref::<tracing::subscriber::SetGlobalDefaultError>().is_some()
);
println!(
" downcast to TryInitError: {}",
e.downcast_ref::<tracing_subscriber::util::TryInitError>().is_some()
);
}
}
tracing::info!("tracing event");
log::info!("log record");
}
(Other 同上,打印时加前缀 [other logger]。)三种情形连跑 3 遍,输出相同:
$ ./which_err none
try_init -> Ok
INFO which_err: tracing event
INFO which_err: log record
$ ./which_err global-taken
try_init -> Err, Display = a global default trace dispatcher has already been set
source() = None
downcast to SetGlobalDefaultError: false
downcast to TryInitError: true
$ ./which_err logger-taken
try_init -> Err, Display = attempted to set a logger after the logging system was already initialized
source() = None
downcast to SetGlobalDefaultError: false
downcast to TryInitError: true
INFO which_err: tracing event
[other logger] log record
global-taken:两条日志都没了。tracing 事件去了先装的NoSubscriber;log::info!也没了,因为第一个?返回后LogTracer根本没装,log没有 logger。logger-taken:Err,但 tracing 事件照常输出,log记录走了先装的 logger。- 两种情形的错误类型一样,都是
TryInitError。它把真正的错误装在私有字段inner: Box<dyn Error + ..>里(TryInitError),没有取出它的方法;Error::source的实现(第 152 行)返回的是self.inner.source(),即内层错误的 source,而两种内层错误都没有 source,所以是None。能区分它们的只剩Display/Debug的文字。
问: 那就判断 is_err(),失败了再装一次?
答: 两种失败都不能靠「再装一次」解决。全局默认一旦设上就不能换(set_global_default 用 compare_exchange 只放一个线程过去,见 Dispatch:全局与线程局部);log 的 logger 也是只能设一次。而且 logger-taken 时你的 subscriber 已经装好,再装只会得到第一种错误。真正要做的是弄清楚是谁先装的:通常是一个库在初始化时顺手装了 subscriber 或 log logger,或者是框架:dioxus-server 0.7.9 的 launch.rs 里就调用了 dioxus_logger::initialize_default(),本站因此碰到的情形见 subscriber 的初始化。
测试里每个测试都 init
libtest 默认在同一个进程的多个线程里并行跑测试,全局默认是进程级的。每个测试都调用一次 setup():
fn setup() {
tracing_subscriber::fmt().with_test_writer().init();
}
#[test]
fn first() {
setup();
tracing::info!("first");
}
#[test]
fn second() {
setup();
tracing::info!("second");
}
cargo test --release 连跑 3 次,失败的测试第 1、3 次是 second,第 2 次是 first,取决于谁先抢到。第 1 次:
running 2 tests
. 1/2
second --- FAILED
failures:
---- second stdout ----
thread 'second' (1572368) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tracing-subscriber-0.3.23/src/fmt/mod.rs:520:14:
Unable to install global subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
second
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
只跑其中一个测试时它会通过,于是这个问题常常在加第二个测试时才出现。改法是让每个测试装一个线程默认,用守卫限定在这个测试里:
use tracing_subscriber::util::SubscriberInitExt;
fn setup() -> tracing::subscriber::DefaultGuard {
tracing_subscriber::fmt().with_test_writer().set_default()
}
#[test]
fn first() {
let _guard = setup();
tracing::info!("first");
}
#[test]
fn second() {
let _guard = setup();
tracing::info!("second");
}
连跑 3 次都是 test result: ok. 2 passed; 0 failed。libtest 每个测试跑在自己的线程上,线程默认互不干扰;守卫要绑到 _guard 这样的变量上,不能写 let _ =(原因见 一条日志都没有)。测试里的输出为什么要 with_test_writer(),见 测试里的日志。
修法
| 情形 | 怎么改 |
|---|---|
自己的代码 init 了两次 | 只在 main 开头初始化一次;库代码不要装全局 subscriber,交给二进制决定 |
测试里每个测试都 init | 每个测试 let _guard = ...set_default();;或者只要求「装上就行」时用 let _ = try_init(),这是 let _ = 唯一说得过去的地方 |
| 依赖或框架先装了全局 subscriber | 找到它(见下一节),用它提供的配置入口,或者关掉它的初始化 |
只是 log 的 logger 被占了 | 不需要 log 转发就关掉 tracing-subscriber 的 tracing-log feature(default-features = false 再列出要的 feature);需要的话把两步拆开写,见下面 |
想分清两种失败,不要用 try_init,自己分两步做,每一步的错误类型都是具体的:
fn main() {
log::set_logger(&Other).unwrap(); // 别人先占了 log
log::set_max_level(log::LevelFilter::Info);
let subscriber = tracing_subscriber::fmt().with_ansi(false).without_time().finish();
// 第一件事:装全局 subscriber,错误类型是 SetGlobalDefaultError
if let Err(e) = tracing::subscriber::set_global_default(subscriber) {
eprintln!("tracing subscriber 没装上: {e}");
}
// 第二件事:把 log 转进 tracing,错误类型是 log::SetLoggerError
if let Err(e) = tracing_log::LogTracer::init() {
eprintln!("LogTracer 没装上(tracing 本身已装好): {e}");
}
tracing::info!("tracing event");
log::info!("log record");
}
(另加 tracing-log = "=0.2.0"。)连跑 3 次输出相同:
LogTracer 没装上(tracing 本身已装好): attempted to set a logger after the logging system was already initialized
INFO typed_init: tracing event
[other logger] log record
LogTracer::init() 不像 try_init 那样把 log 的最大级别同步成 tracing 的;要同步,用 LogTracer::builder().with_max_level(..).init(),和上面源码里的写法一样。
怎么确认自己没踩
不确定谁先装了 subscriber 时,在你的
init之前打印tracing::dispatcher::has_been_set()。它在任何 dispatcher 被设过(全局或线程默认)后返回true(has_been_set)。panic 的位置在 tracing-subscriber 里,要找你自己的那一行,用 debug 构建加
RUST_BACKTRACE=1。上面builder那个例子在 debug 构建下的回溯里有一帧:5: init_twice::main at ./src/bin/init_twice.rs:9:33第 9 行正是第二个
fmt().init()。release 构建里这一帧只剩init_twice::main,没有行号。用了
try_init的地方,至少把错误打出来(if let Err(e) = ... { eprintln!("{e}") }),不要let _ =。看到a global default trace dispatcher has already been set就是没装上;看到attempted to set a logger ...就是装上了、只差log转发。
小结
- 两段 panic 文字对应两个
init:SubscriberBuilder::init(fmt().init())是Unable to install global subscriber,SubscriberInitExt::init(registry().with(..).init())是failed to set global default subscriber;冒号后是内层错误的Debug。 try_init有两步:set_global_default失败时什么都没装;LogTracer失败时 subscriber 已经装上。第二种情况 panic 文字仍然说「Unable to install」。TryInitError不暴露内层错误,source()是None,代码里只能靠文字区分;要区分就自己分两步装。- libtest 并行跑测试,全局默认是进程级的,每个测试都
init必有一个 panic;测试里用set_default加守卫。 - panic 位置在 tracing-subscriber 内部,用 debug 构建的回溯找自己的调用行。