Rust中tracing库与daemonize结合后日志丢失问题求助
嗨,这个问题我之前也踩过坑!核心原因和daemonize的工作机制、tracing_appender的non_blocking实现脱不了干系,咱们一步步说清楚:
为什么"Bar"日志会丢失?
当你调用daemonize::Daemonize::new().start()时,它会执行标准的守护进程化流程:fork出子进程,父进程直接退出,子进程脱离终端成为独立的会话首进程。
而你在daemon化前创建的non_blocking writer,是基于**跨线程通道(mpsc)**实现的:父进程里有通道的发送端,另一个线程拿着接收端负责把日志写入文件。当父进程退出时,发送端会被自动销毁,通道直接关闭——子进程里的tracing再发日志,只能往一个已经关闭的通道里塞,自然就没人处理这些日志了,"Bar"也就丢了。
而log库之所以能正常工作,是因为它默认的实现大多是同步直接写文件,不需要依赖跨线程通道,daemon化后只要文件描述符还保留着,就能继续写入。
两种解决办法,看你需求选:
方案1:先daemon化,再初始化tracing(更推荐)
把tracing的初始化逻辑放到daemon化完成之后,这样所有日志组件都是在子进程里创建的,完全不受父进程退出的影响:
use tracing::info; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::{fmt, Registry}; fn main() { // 先完成守护进程化 daemonize::Daemonize::new().start().unwrap(); // 在子进程里初始化tracing let file_appender = tracing_appender::rolling::never("/tmp", "test.log"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); let subscriber = Registry::default().with(fmt::Layer::default().with_writer(non_blocking)); tracing::subscriber::set_global_default(subscriber).unwrap(); info!("Bar"); // 现在这个日志能正常写入了 // 要是需要保留"Foo"的日志,可以在daemon化前临时输出到终端,或者也写入文件(注意不要覆盖) }
方案2:用同步文件写入器代替non_blocking
如果你不想调整初始化顺序,可以试试直接用同步的file appender,这样不需要依赖跨线程通道,daemon化后只要文件没被关闭就能继续写:
use tracing::info; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::{fmt, Registry}; fn main() { let file_appender = tracing_appender::rolling::never("/tmp", "test.log"); // 直接用同步writer,跳过non_blocking let subscriber = Registry::default().with(fmt::Layer::default().with_writer(file_appender)); tracing::subscriber::set_global_default(subscriber).unwrap(); info!("Foo"); daemonize::Daemonize::new().start().unwrap(); info!("Bar"); // 现在这个日志也能正常写入了 }
不过同步写入会影响高频日志场景的性能,所以更推荐第一种方案。
关于tracing_log的适配
如果你要结合tracing_log把原有log库的日志转到tracing里,只要在初始化tracing subscriber之后加上LogTracer的初始化就行,比如:
use tracing::info; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::{fmt, Registry}; fn main() { daemonize::Daemonize::new().start().unwrap(); // 初始化tracing_log,把log的日志转发到tracing系统 tracing_log::LogTracer::init().unwrap(); let file_appender = tracing_appender::rolling::never("/tmp", "test.log"); let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); let subscriber = Registry::default().with(fmt::Layer::default().with_writer(non_blocking)); tracing::subscriber::set_global_default(subscriber).unwrap(); info!("Bar"); // 原来用log::info!的代码也会自动写到这个日志文件里 }
最后提醒一句:daemon化后进程的工作目录会变,一定要用绝对路径指定日志文件(你已经用了/tmp,这点没问题),不然可能会找不到文件哦~
备注:内容来源于stack exchange,提问作者mfld

