fix: initialize tracing only in daemon mode, tee to daemon.log

- 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
pull/43/head
David Li 2026-05-13 16:34:28 +08:00
parent 3afb88920c
commit eebc9e97b7
1 changed files with 51 additions and 28 deletions

View File

@ -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<Arc<Mutex<Option<File>>>> = OnceLock::new();
let file_entry = LOG_FILE.get_or_init(|| {
let _ = std::fs::create_dir_all(config::cli_dir());
let log_file = std::fs::OpenOptions::new()
Arc::new(Mutex::new(
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(config::log_path())
.ok();
.ok(),
))
});
match log_file {
Some(file) => {
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(file)
.init();
}
None => {
// 文件打开失败时退回到 stderr确保日志不会静默丢失
tracing_subscriber::fmt()
.with_target(false)
.with_level(true)
.with_env_filter(env_filter)
.with_writer(std::io::stderr)
.with_writer(move || {
let file_entry = Arc::clone(file_entry);
let guard = file_entry.lock().unwrap();
let b: Box<dyn std::io::Write + Send> = guard
.as_ref()
.and_then(|f| f.try_clone().ok())
.map(|f| Box::new(f) as Box<dyn std::io::Write + Send>)
.unwrap_or_else(|| Box::new(std::io::stderr()));
TeeWriter {
a: Box::new(std::io::stderr()),
b,
}
})
.init();
}
/// 同时写入两个 Write 目标
struct TeeWriter {
a: Box<dyn std::io::Write + Send>,
b: Box<dyn std::io::Write + Send>,
}
impl std::io::Write for TeeWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let _ = self.b.write_all(buf);
self.a.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
let _ = self.b.flush();
self.a.flush()
}
}