hope_cache_log/
lib.rs

1use std::{
2    fs::File,
3    io::{BufRead, BufReader, BufWriter, Write as _},
4    path::Path,
5};
6
7use anyhow::Context;
8use chrono::Utc;
9use fd_lock::RwLock;
10use serde::{Deserialize, Serialize};
11
12const LOG_FILE_NAME: &str = "hope-log.jsonl";
13
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub enum CacheLogLine {
16    PulledCrateOutputs(PullCrateOutputsEvent),
17    PushedCrateOutputs(PushCrateOutputsEvent),
18    RanBuildScript(BuildScriptRunEvent),
19}
20
21#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct PullCrateOutputsEvent {
23    pub crate_unit_name: String,
24    pub copied_at: chrono::DateTime<Utc>,
25    // Free-form description of where it came from;
26    // may differ depending on the cache implementation.
27    pub copied_from: String,
28    // How long did it take to copy from cache?
29    pub duration_secs: f64,
30}
31
32#[derive(Debug, Serialize, Deserialize, Clone)]
33pub struct PushCrateOutputsEvent {
34    pub crate_unit_name: String,
35    pub copied_at: chrono::DateTime<Utc>,
36    // Free-form description of where it went to;
37    // may differ depending on the cache implementation.
38    pub copied_from: String,
39    // How long did it take to copy to cache?
40    pub duration_secs: f64,
41}
42
43// TODO: The existence of this kinda suggests that this log
44// should probably not be associated with a specific cache,
45// but be global by default (with ability to override).
46#[derive(Debug, Serialize, Deserialize, Clone)]
47pub struct BuildScriptRunEvent {
48    // TODO: Lots of other details
49    pub ran_at: chrono::DateTime<Utc>,
50}
51
52pub fn write_log_line(cache_dir: &Path, log_line: CacheLogLine) -> anyhow::Result<()> {
53    let file = File::options()
54        .create(true)
55        .append(true)
56        .open(cache_dir.join(LOG_FILE_NAME))?;
57    let mut file = RwLock::new(file);
58    let mut write_guard = file.write()?;
59    let mut writer = BufWriter::new(&mut *write_guard);
60    serde_json::to_writer(&mut writer, &log_line)?;
61    writeln!(&mut writer)?;
62    writer.flush()?;
63
64    Ok(())
65}
66
67pub fn read_log(cache_dir: &Path) -> anyhow::Result<Vec<CacheLogLine>> {
68    let mut log = Vec::new();
69    let file = File::open(cache_dir.join(LOG_FILE_NAME))?;
70    let mut file = RwLock::new(file);
71    let mut read_guard = file.write()?;
72    let reader = BufReader::new(&mut *read_guard);
73
74    for line in reader.lines() {
75        let line = line?;
76        log.push(
77            serde_json::from_str(&line)
78                .with_context(|| format!("Failed to deserialize log line:\n{line}"))?,
79        );
80    }
81    Ok(log)
82}