fix(daemon): log fatal startup errors to daemon.log
A daemon that fails during startup reported the reason via eprintln! in main's error arm. Under launchd stderr is discarded, so the failure was invisible: daemon.log was created and left empty — exactly the case the log file was added for. Found by the manual acceptance run: the launchd agent loaded correctly (RunAtLoad fired, runs=9) but every spawn exited 1 because a previously started daemon held the pidfile, and nothing recorded why. Fatal errors on the daemon path now go through tracing::error!, reaching both the log file and stderr. Other subcommands keep eprintln!, since their stderr is the user's terminal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EGntTHCW3sEPy1VBRopNNp
This commit is contained in:
+12
-3
@@ -92,7 +92,9 @@ async fn main() -> std::process::ExitCode {
|
|||||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
|
||||||
|
|
||||||
if matches!(cli.cmd, Cmd::Daemon) {
|
let is_daemon = matches!(cli.cmd, Cmd::Daemon);
|
||||||
|
|
||||||
|
if is_daemon {
|
||||||
use tracing_subscriber::fmt::writer::MakeWriterExt;
|
use tracing_subscriber::fmt::writer::MakeWriterExt;
|
||||||
|
|
||||||
if let Err(err) = paths.ensure_dirs() {
|
if let Err(err) = paths.ensure_dirs() {
|
||||||
@@ -134,8 +136,15 @@ async fn main() -> std::process::ExitCode {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(code) => std::process::ExitCode::from(code as u8),
|
Ok(code) => std::process::ExitCode::from(code as u8),
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
eprintln!("xy: {e:#}");
|
// Under launchd the daemon's stderr is discarded, so a fatal
|
||||||
|
// startup error is only diagnosable if it reaches the log file.
|
||||||
|
if is_daemon {
|
||||||
|
tracing::error!("{err:#}");
|
||||||
|
} else {
|
||||||
|
eprintln!("xy: {err:#}");
|
||||||
|
}
|
||||||
|
|
||||||
std::process::ExitCode::from(1)
|
std::process::ExitCode::from(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fatal_startup_error_is_written_to_daemon_log() {
|
||||||
|
let xy = xy_bin();
|
||||||
|
let mut h = Harness::new();
|
||||||
|
|
||||||
|
h.start_daemon(&xy).await;
|
||||||
|
|
||||||
|
let log = h.state_dir.join("logs/daemon.log");
|
||||||
|
|
||||||
|
let before = std::fs::read_to_string(&log).unwrap_or_default();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!before.contains("another xy daemon"),
|
||||||
|
"log already reports contention before the second daemon ran: {before}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (code, _out, _err) = h.run_cli(&xy, &["daemon"]).await;
|
||||||
|
|
||||||
|
assert_eq!(code, 1, "second daemon should exit 1 on pidfile contention");
|
||||||
|
|
||||||
|
let after = std::fs::read_to_string(&log).expect("daemon.log should exist");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
after.contains("another xy daemon"),
|
||||||
|
"daemon.log must record why the daemon refused to start, got: {after:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user