From 17edb1db2ba2bf3c0f29c2c3f22e33e4ad1ecf6e Mon Sep 17 00:00:00 2001 From: emozilla Date: Thu, 28 May 2026 10:48:43 -0400 Subject: [PATCH] fix(installer): bump bootstrap-installer.log to capture stage transitions + every install.ps1 line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diagnosing the second VM failure was impossible because bootstrap-installer.log contained only the 'starting' banner. Two causes: 1. emit_log() inside run_bootstrap() was tracing::debug! — dropped on the floor under the default INFO env-filter. 2. The per-stage sink callbacks (on_stdout_line / on_stderr_line) only emitted Tauri events to the frontend; they never tee'd to the log file at all. When the failure route mounts, the Tauri event stream is the only place the script output lived, and it gets discarded. 3. The Failed / Stage / Manifest / Complete lifecycle frames in emit_event() were also Tauri-only — so even the 'which stage failed' frame never reached the log. Fixes: * emit_log() → tracing::info! * Sink callbacks tee stdout to info!, stderr to warn!, with stage label as a structured field for grep'ability * emit_event() now matches on the variant and logs each lifecycle frame at the right level: Failed → tracing::error!, others → info! Result: a failing install leaves a complete forensic trail in bootstrap-installer.log — manifest stage list, every install.ps1 stdout/stderr line tagged by stage, the stage transitions, and the final error. Same path as before so nothing the user does changes. --- .../src-tauri/src/bootstrap.rs | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/bootstrap-installer/src-tauri/src/bootstrap.rs b/apps/bootstrap-installer/src-tauri/src/bootstrap.rs index c6bd1a6b810..94fe43304a0 100644 --- a/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +++ b/apps/bootstrap-installer/src-tauri/src/bootstrap.rs @@ -264,7 +264,11 @@ async fn run_bootstrap( line: line.to_string(), }, ); - tracing::debug!(target: "bootstrap.log", "{line}"); + // Bump to info-level so the line shows in bootstrap-installer.log + // under the default INFO filter. Previously this was debug! which + // got dropped on the floor, leaving us blind whenever install.ps1 + // failed — the log only had the "bootstrap starting" banner. + tracing::info!(target: "bootstrap.log", "{line}"); }; // 1. Resolve install.ps1 @@ -570,6 +574,8 @@ async fn run_install_script( let stage_for_stdout = stage_name.clone(); let app_for_stderr = app.clone(); let stage_for_stderr = stage_name.clone(); + let stage_for_stdout_log = stage_name.clone(); + let stage_for_stderr_log = stage_name.clone(); let sink = StreamSink { on_stdout_line: Box::new(move |line: &str| { @@ -580,6 +586,16 @@ async fn run_install_script( line: line.to_string(), }, ); + // Tee to the rolling installer log so we have a persistent + // record of every install.ps1 line. Without this, the only + // log evidence of a failure was the Tauri event stream — + // which gets discarded the moment the failure route mounts. + match &stage_for_stdout_log { + Some(name) => { + tracing::info!(target: "bootstrap.log", stage = %name, "{line}") + } + None => tracing::info!(target: "bootstrap.log", "{line}"), + } }), on_stderr_line: Box::new(move |line: &str| { emit_event( @@ -589,6 +605,14 @@ async fn run_install_script( line: format!("stderr: {line}"), }, ); + // stderr-level lines get warn! so they're visually distinct + // when scrolling through the log later. + match &stage_for_stderr_log { + Some(name) => { + tracing::warn!(target: "bootstrap.log", stage = %name, "stderr: {line}") + } + None => tracing::warn!(target: "bootstrap.log", "stderr: {line}"), + } }), }; @@ -614,6 +638,44 @@ fn build_pin_args(script: &install_script::ResolvedScript) -> Vec { } fn emit_event(app: &AppHandle, event: BootstrapEvent) { + // Tee important state transitions to the rolling installer log so + // bootstrap-installer.log isn't just "starting" + final summary. + // Log lines (the noisy stuff) handle their own tracing in + // run_install_script's sink; here we cover the lifecycle frames. + match &event { + BootstrapEvent::Manifest { stages, .. } => { + tracing::info!( + stage_count = stages.len(), + names = ?stages.iter().map(|s| s.name.as_str()).collect::>(), + "manifest received" + ); + } + BootstrapEvent::Stage { + name, + state, + duration_ms, + error, + .. + } => { + tracing::info!( + stage = %name, + ?state, + duration_ms = ?duration_ms, + error = ?error, + "stage transition" + ); + } + BootstrapEvent::Complete { install_root, .. } => { + tracing::info!(install_root = %install_root, "bootstrap complete"); + } + BootstrapEvent::Failed { stage, error } => { + tracing::error!(stage = ?stage, error = %error, "bootstrap FAILED"); + } + BootstrapEvent::Log { .. } => { + // Log lines are teed via the sink callbacks in + // run_install_script — don't double-emit here. + } + } if let Err(e) = app.emit(BootstrapEvent::CHANNEL, &event) { tracing::warn!(?e, "failed to emit bootstrap event"); }