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()
|
||||
.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;
|
||||
|
||||
if let Err(err) = paths.ensure_dirs() {
|
||||
@@ -134,8 +136,15 @@ async fn main() -> std::process::ExitCode {
|
||||
|
||||
match result {
|
||||
Ok(code) => std::process::ExitCode::from(code as u8),
|
||||
Err(e) => {
|
||||
eprintln!("xy: {e:#}");
|
||||
Err(err) => {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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