Log CRI log read errors instead of silently swallowing them
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)
}