fix(installer): bump bootstrap-installer.log to capture stage transitions + every install.ps1 line

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.
This commit is contained in:
emozilla 2026-05-28 10:48:43 -04:00
parent d5fe467277
commit 17edb1db2b

View file

@ -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<String> {
}
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::<Vec<_>>(),
"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");
}