diff --git a/crates/xy/src/daemon/mod.rs b/crates/xy/src/daemon/mod.rs index 02a546e..ddcd70a 100644 --- a/crates/xy/src/daemon/mod.rs +++ b/crates/xy/src/daemon/mod.rs @@ -66,8 +66,6 @@ pub fn spawn_supervisor(paths: &Paths, cfg: ServerConfig) -> Result Result<()> { - paths.ensure_dirs().context("create state dirs")?; - let _pid = PidFile::acquire(&paths.pidfile).context("another xy daemon appears to be running")?; diff --git a/crates/xy/src/logging.rs b/crates/xy/src/logging.rs new file mode 100644 index 0000000..cfd6512 --- /dev/null +++ b/crates/xy/src/logging.rs @@ -0,0 +1,32 @@ +use std::path::Path; +use std::sync::{Arc, Mutex}; +use xy_supervisor::logs::RotatingLogWriter; + +const LOG_FILE_MAX_BYTES: u64 = 10 * 1024 * 1024; +const LOG_FILE_KEEP: usize = 5; + +pub(crate) fn daemon_writer(log_dir: &Path) -> std::io::Result>> { + let writer = + RotatingLogWriter::open(&log_dir.join("xy.log"), LOG_FILE_MAX_BYTES, LOG_FILE_KEEP)?; + + Ok(Arc::new(Mutex::new(writer))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn daemon_writer_creates_and_appends_to_xy_log() { + use std::io::Write; + + let tmp = tempfile::tempdir().unwrap(); + + let writer = daemon_writer(tmp.path()).unwrap(); + + writer.lock().unwrap().write_all(b"line\n").unwrap(); + + let contents = std::fs::read_to_string(tmp.path().join("xy.log")).unwrap(); + assert_eq!(contents, "line\n"); + } +} diff --git a/crates/xy/src/main.rs b/crates/xy/src/main.rs index 53ad0db..00e05b0 100644 --- a/crates/xy/src/main.rs +++ b/crates/xy/src/main.rs @@ -1,7 +1,9 @@ use clap::{Parser, Subcommand}; +use std::sync::Arc; mod cli; mod daemon; +mod logging; mod paths; mod pidfile; @@ -55,23 +57,49 @@ enum Cmd { #[tokio::main] async fn main() -> std::process::ExitCode { - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), - ) - .with_writer(std::io::stderr) - .init(); - let cli = Cli::parse(); + let paths = match paths::Paths::resolve() { Ok(p) => p, - Err(e) => { - eprintln!("xy: failed to resolve XDG paths: {e}"); + Err(err) => { + eprintln!("xy: failed to resolve XDG paths: {err}"); return std::process::ExitCode::from(3); } }; + let filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); + + if matches!(cli.cmd, Cmd::Daemon) { + use tracing_subscriber::fmt::writer::MakeWriterExt; + + if let Err(err) = paths.ensure_dirs() { + eprintln!("xy: failed to create state dirs: {err}"); + return std::process::ExitCode::from(3); + } + + let file = match logging::daemon_writer(&paths.log_dir) { + Ok(writer) => writer, + Err(err) => { + eprintln!("xy: failed to open daemon log: {err}"); + return std::process::ExitCode::from(3); + } + }; + + let file = Arc::into_inner(file).expect("daemon_writer arc has one owner"); + + tracing_subscriber::fmt() + .with_env_filter(filter) + .with_ansi(false) + .with_writer(std::io::stderr.and(file)) + .init(); + } else { + tracing_subscriber::fmt() + .with_env_filter(filter) + .with_writer(std::io::stderr) + .init(); + } + let result: anyhow::Result = match cli.cmd { Cmd::Daemon => daemon::run(paths).await.map(|_| 0), Cmd::List => cli::list(paths).await,