From eebc9e97b7eda643af614730fb5dadf9922e7593 Mon Sep 17 00:00:00 2001 From: David Li Date: Wed, 13 May 2026 16:34:28 +0800 Subject: [PATCH] fix: initialize tracing only in daemon mode, tee to daemon.log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move init_logging() into daemon-only branch — CLI path doesn't need tracing and WARN-level output could corrupt JSON/YAML stdout - Add TeeWriter that duplicates tracing output to both stderr and ~/.wx-cli/daemon.log, ensuring direct invocation (WX_DAEMON_MODE=1 without 'wx daemon start') also writes to the log file - Keep run_start() stderr redirect as primary path; file writer is a safety net for edge-case direct launches --- src/main.rs | 79 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4d99dc8..6ff6cf9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,38 +18,61 @@ fn main() { fn init_logging() { use tracing_subscriber::EnvFilter; + use std::sync::{Arc, Mutex, OnceLock}; + use std::fs::File; // CLI 路径不需要 tracing — 只输出用户可见的 stdout/stderr。 - // daemon 路径:tracing 直接写入 ~/.wx-cli/daemon.log, - // 不依赖父进程的 stderr 重定向(避免重复写入)。 - let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| { - EnvFilter::new("info") + // daemon 路径:stderr 已通过 run_start() 重定向到 daemon.log, + // 但我们也直接写入日志文件以覆盖直接设置 WX_DAEMON_MODE=1 的情况。 + static LOG_FILE: OnceLock>>> = OnceLock::new(); + let file_entry = LOG_FILE.get_or_init(|| { + let _ = std::fs::create_dir_all(config::cli_dir()); + Arc::new(Mutex::new( + std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(config::log_path()) + .ok(), + )) }); - let _ = std::fs::create_dir_all(config::cli_dir()); - let log_file = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(config::log_path()) - .ok(); + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| { + EnvFilter::new("warn") + }); + tracing_subscriber::fmt() + .with_target(false) + .with_level(true) + .with_env_filter(env_filter) + .with_writer(move || { + let file_entry = Arc::clone(file_entry); + let guard = file_entry.lock().unwrap(); + let b: Box = guard + .as_ref() + .and_then(|f| f.try_clone().ok()) + .map(|f| Box::new(f) as Box) + .unwrap_or_else(|| Box::new(std::io::stderr())); + TeeWriter { + a: Box::new(std::io::stderr()), + b, + } + }) + .init(); +} - match log_file { - Some(file) => { - tracing_subscriber::fmt() - .with_target(false) - .with_level(true) - .with_env_filter(env_filter) - .with_writer(file) - .init(); - } - None => { - // 文件打开失败时退回到 stderr,确保日志不会静默丢失 - tracing_subscriber::fmt() - .with_target(false) - .with_level(true) - .with_env_filter(env_filter) - .with_writer(std::io::stderr) - .init(); - } +/// 同时写入两个 Write 目标 +struct TeeWriter { + a: Box, + b: Box, +} + +impl std::io::Write for TeeWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let _ = self.b.write_all(buf); + self.a.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + let _ = self.b.flush(); + self.a.flush() } }