Log CRI log read errors instead of silently swallowing them
change msqwprmmxwxqzmlzxmnxponvytmwqpyy
commit 435a5669470e88c2a4b616b46f1c40f14ffeaeee
author Alpha Chen <alpha@kejadlen.dev>
date
parent vrmukzts
diff --git a/src/ci/logs.rs b/src/ci/logs.rs
index 2c734a1..97ce094 100644
--- a/src/ci/logs.rs
+++ b/src/ci/logs.rs
@@ -20,7 +20,7 @@ use super::runtime::ShOutput;
 pub fn write_cri_log(path: &Path, output: &ShOutput, ts: &str) -> std::io::Result<()> {
     use std::io::Write;
 
-    let mut f = std::fs::File::create(path)?;
+    let mut f = fs_err::File::create(path)?;
 
     for line in output.stdout.lines() {
         writeln!(f, "{ts} stdout F {line}")?;
diff --git a/src/ci/run.rs b/src/ci/run.rs
index e8fd60a..d12623b 100644
--- a/src/ci/run.rs
+++ b/src/ci/run.rs
@@ -441,7 +441,7 @@ impl Run {
     fn write_sh_records(
         &self,
         outputs: &HashMap<String, Vec<ShOutput>>,
-        timings: &HashMap<String, Vec<(usize, jiff::Timestamp, jiff::Timestamp)>>,
+        timings: &HashMap<String, super::runtime::ShTimings>,
     ) -> Result<()> {
         if outputs.is_empty() {
             return Ok(());
@@ -467,11 +467,7 @@ impl Run {
                 // Write CRI log file.
                 fs_err::create_dir_all(&job_dir)?;
                 let sh_path = job_dir.join(format!("sh-{n}.log"));
-                super::logs::write_cri_log(
-                    &sh_path,
-                    output,
-                    &started_at.to_string(),
-                )?;
+                super::logs::write_cri_log(&sh_path, output, &started_at.to_string())?;
 
                 // Insert sh event into the database.
                 db.execute(
@@ -647,7 +643,6 @@ pub fn materialize_workspace(git_dir: &Path, sha: &str, workspace: &Path) -> Res
     Ok(())
 }
 
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/ci/runtime.rs b/src/ci/runtime.rs
index 518de9c..84d4d46 100644
--- a/src/ci/runtime.rs
+++ b/src/ci/runtime.rs
@@ -10,13 +10,16 @@ use std::cell::RefCell;
 use std::collections::HashMap;
 use std::rc::Rc;
 
-use mlua::{IntoLua, Lua, LuaSerdeExt};
 use jiff::Timestamp;
+use mlua::{IntoLua, Lua, LuaSerdeExt};
 
 use super::pipeline::{Job, Pipeline};
 use super::run::{DockerLifecycle, RunMeta};
 use crate::secret::SecretString;
 
+/// Per-sh timing: (index, started_at, finished_at).
+pub(super) type ShTimings = Vec<(usize, Timestamp, Timestamp)>;
+
 /// The runtime-side carrier for the chosen [`Executor`](super::run::Executor).
 /// `Host` runs `sh` directly on the host. `Docker` owns a
 /// [`DockerLifecycle`] whose Drop tears down the per-run container;
@@ -56,7 +59,7 @@ pub(super) struct Runtime {
     pub(super) outputs: RefCell<HashMap<String, Vec<ShOutput>>>,
     /// Per-sh timing records: job_id → (sh_index, started_at, finished_at).
     /// Parallel to `outputs`; each entry at the same index corresponds.
-    pub(super) sh_timings: RefCell<HashMap<String, Vec<(usize, Timestamp, Timestamp)>>>,
+    pub(super) sh_timings: RefCell<HashMap<String, ShTimings>>,
     /// Per-job sh call counter for assigning sequential indices.
     sh_counter: RefCell<HashMap<String, usize>>,
     /// The materialized workspace for this run. Every `(sh …)` call
@@ -187,7 +190,7 @@ impl Runtime {
     }
 
     /// Drain all recorded sh timings, returning them keyed by job id.
-    pub(super) fn take_sh_timings(&self) -> HashMap<String, Vec<(usize, Timestamp, Timestamp)>> {
+    pub(super) fn take_sh_timings(&self) -> HashMap<String, ShTimings> {
         std::mem::take(&mut *self.sh_timings.borrow_mut())
     }
 
diff --git a/src/quire/web.rs b/src/quire/web.rs
index 2ee8ebb..a23fe7e 100644
--- a/src/quire/web.rs
+++ b/src/quire/web.rs
@@ -54,7 +54,7 @@ pub async fn run_list(
 }
 
 fn load_runs(quire: &Quire, repo: &str) -> Result<Vec<RunRow>, String> {
-    let db = Connection::open(&quire.db_path()).map_err(|e| e.to_string())?;
+    let db = Connection::open(quire.db_path()).map_err(|e| e.to_string())?;
     let mut stmt = db
         .prepare(
             "SELECT id, state, sha, ref_name, queued_at_ms, started_at_ms, finished_at_ms
@@ -135,8 +135,13 @@ pub async fn run_detail(
             .join(&ev.job_id)
             .join(format!("sh-{sh_n}.log"));
         if log_path.exists() {
-            if let Ok(content) = std::fs::read_to_string(&log_path) {
-                log_contents.insert(key, content);
+            match fs_err::read_to_string(&log_path) {
+                Ok(content) => {
+                    log_contents.insert(key, content);
+                }
+                Err(e) => {
+                    tracing::warn!(path = %log_path.display(), error = %e, "failed to read CRI log");
+                }
             }
         }
     }
@@ -166,7 +171,7 @@ fn load_run_detail(
     repo: &str,
     run_id: &str,
 ) -> Result<(RunRow, Vec<JobRow>, Vec<ShEvent>), String> {
-    let db = Connection::open(&quire.db_path()).map_err(|e| e.to_string())?;
+    let db = Connection::open(quire.db_path()).map_err(|e| e.to_string())?;
 
     let run = db
         .query_row(
@@ -399,8 +404,8 @@ sh-{sh_n} · {ev_duration} · exit {exit_code}
     }
 
     if jobs.is_empty() {
-        jobs_html = r#"<div style="padding:16px 0;color:var(--muted)">no jobs recorded</div>"#
-            .to_string();
+        jobs_html =
+            r#"<div style="padding:16px 0;color:var(--muted)">no jobs recorded</div>"#.to_string();
     }
 
     let style = css();
@@ -544,10 +549,7 @@ fn html_escape(s: &str) -> String {
 pub fn router(quire: Quire) -> axum::Router {
     axum::Router::new()
         .route("/repo/{repo}/ci", axum::routing::get(run_list))
-        .route(
-            "/repo/{repo}/ci/{run_id}",
-            axum::routing::get(run_detail),
-        )
+        .route("/repo/{repo}/ci/{run_id}", axum::routing::get(run_detail))
         .with_state(quire)
 }