diff --git a/src/cli/transport.rs b/src/cli/transport.rs index eaedf81..7c3c21d 100644 --- a/src/cli/transport.rs +++ b/src/cli/transport.rs @@ -114,6 +114,8 @@ pub fn ensure_daemon(tcp_addr: Option<&str>) -> Result<()> { } /// 启动 daemon 进程(自身二进制,设置 WX_DAEMON_MODE=1) +/// +/// tracing 已在子进程 main() 中直接写入 daemon.log,无需重定向 stdout/stderr。 fn start_daemon() -> Result<()> { let exe = std::env::current_exe().context("无法获取当前可执行文件路径")?; @@ -124,23 +126,11 @@ fn start_daemon() -> Result<()> { #[cfg(unix)] { use std::os::unix::process::CommandExt; - // 日志文件:~/.wx-cli/daemon.log - let log_path = config::log_path(); - // 确保父目录存在 - if let Some(parent) = log_path.parent() { - let _ = std::fs::create_dir_all(parent); - } - let (stdout_stdio, stderr_stdio) = std::fs::OpenOptions::new() - .create(true).append(true) - .open(&log_path) - .and_then(|f| f.try_clone().map(|g| (f, g))) - .map(|(f, g)| (std::process::Stdio::from(f), std::process::Stdio::from(g))) - .unwrap_or_else(|_| (std::process::Stdio::null(), std::process::Stdio::null())); let mut cmd = std::process::Command::new(&exe); cmd.env("WX_DAEMON_MODE", "1") .stdin(std::process::Stdio::null()) - .stdout(stdout_stdio) - .stderr(stderr_stdio); + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()); // SAFETY: setsid() 在 fork 后的子进程中调用,使 daemon 脱离控制终端 unsafe { cmd.pre_exec(|| { libc::setsid(); Ok(()) }); } let _ = cmd.spawn().context("无法启动 daemon 进程")?; @@ -149,50 +139,16 @@ fn start_daemon() -> Result<()> { #[cfg(windows)] { use std::os::windows::process::CommandExt; - let log_path = config::log_path(); - if let Some(parent) = log_path.parent() { - let _ = std::fs::create_dir_all(parent); - } - let (stdout_stdio, stderr_stdio) = std::fs::OpenOptions::new() - .create(true).append(true) - .open(&log_path) - .and_then(|f| f.try_clone().map(|g| (f, g))) - .map(|(f, g)| (std::process::Stdio::from(f), std::process::Stdio::from(g))) - .unwrap_or_else(|_| (std::process::Stdio::null(), std::process::Stdio::null())); let _ = std::process::Command::new(&exe) .env("WX_DAEMON_MODE", "1") .stdin(std::process::Stdio::null()) - .stdout(stdout_stdio) - .stderr(stderr_stdio) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) .creation_flags(0x00000008) // DETACHED_PROCESS .spawn() .context("无法启动 daemon 进程")?; } - // 等待 daemon 就绪(最多 STARTUP_TIMEOUT_SECS 秒) - let deadline = std::time::Instant::now() + Duration::from_secs(STARTUP_TIMEOUT_SECS); - while std::time::Instant::now() < deadline { - std::thread::sleep(Duration::from_millis(300)); - if is_alive(None) { - return Ok(()); - } - } - - bail!( - "wx-daemon 启动超时(>{}s)\n请查看日志: {}", - STARTUP_TIMEOUT_SECS, - config::log_path().display() - ) -} - -/// 启动 daemon 前检查 `~/.wx-cli/` 可写,给出比"超时"更明确的错误。 -/// -/// 典型坑:旧版本 `sudo wx init` 把目录留成 root 属主,非 root 的 daemon -/// 连 socket/log 都建不了,会静默失败 15s 超时。 -fn preflight_cli_dir_writable() -> Result<()> { - let cli_dir = config::cli_dir(); - std::fs::create_dir_all(&cli_dir) - .with_context(|| format!("创建 {} 失败", cli_dir.display()))?; let probe = cli_dir.join(".daemon_probe"); match std::fs::File::create(&probe) { diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 6232fbe..2064c08 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -23,21 +23,15 @@ pub fn run() { /// 从 CLI `wx daemon start [--tcp ADDR]` 调用 /// /// 查找当前可执行文件路径,设置 WX_DAEMON_MODE=1,后台启动新进程。 +/// tracing 已在子进程 main() 中直接写入 daemon.log,无需重定向 stdout/stderr。 pub fn run_start(tcp_addr: Option) -> Result<()> { let exe = std::env::current_exe()?; - let log = config::log_path(); let mut cmd = std::process::Command::new(&exe); cmd.env("WX_DAEMON_MODE", "1"); if let Some(addr) = &tcp_addr { cmd.env("WX_DAEMON_TCP_ADDR", addr); } - // 日志重定向 - let log_file = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log)?; - cmd.stdout(log_file.try_clone()?).stderr(log_file); #[cfg(unix)] { @@ -88,7 +82,7 @@ pub async fn start_daemon(tcp_addr: Option) -> Result<()> { // 收集消息 DB 列表 let msg_db_keys: Vec = all_keys.keys() .filter(|k| { - let k = k.replace('\\', "/"); + let k = k.replace('\', "/"); k.contains("message/message_") && k.ends_with(".db") && !k.contains("_fts") && !k.contains("_resource") }) @@ -154,7 +148,7 @@ fn extract_keys(json: &serde_json::Value) -> HashMap { }; if !enc_key.is_empty() { // 统一路径分隔符 - let rel = k.replace('\\', "/"); + let rel = k.replace('\', "/"); result.insert(rel, enc_key); } } diff --git a/src/main.rs b/src/main.rs index ffb37cc..1049f6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,8 @@ mod cli; pub mod transport; fn main() { - init_logging(); if std::env::var("WX_DAEMON_MODE").is_ok() { + init_logging(); daemon::run(); } else { cli::run(); @@ -17,14 +17,38 @@ fn main() { fn init_logging() { use tracing_subscriber::EnvFilter; - // 默认只输出 WARN+ 到 stderr(不污染用户可见的 stdout)。 - // 通过 `RUST_LOG=info` 或 `RUST_LOG=debug` 开启详细日志。 + + // 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") }); - tracing_subscriber::fmt() - .with_target(false) - .with_level(true) - .with_env_filter(env_filter) - .init(); + + 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(); + + 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(); + } + } }