#
cargo/util/
logger.rs

    1//! Build analysis logging infrastructure.
    2
    3use std::io::{BufWriter, Write};
    4use std::mem::ManuallyDrop;
    5use std::path::Path;
    6use std::sync::mpsc;
    7use std::sync::mpsc::Sender;
    8use std::thread::JoinHandle;
    9
    10use cargo_util::paths;
    11
    12use crate::CargoResult;
    13use crate::core::Workspace;
    14use crate::util::log_message::LogMessage;
    15use crate::util::short_hash;
    16
    17/// Logger for `-Zbuild-analysis`.
    18pub struct BuildLogger {
    19    tx: ManuallyDrop<Sender<LogMessage>>,
    20    run_id: String,
    21    handle: Option<JoinHandle<()>>,
    22}
    23
    24impl BuildLogger {
    25    /// Creates a logger if `-Zbuild-analysis` is enabled.
    26    pub fn maybe_new(ws: &Workspace<'_>) -> CargoResult<Option<Self>> {
    27        let analysis = ws.gctx().build_config()?.analysis.as_ref();
    28        match (analysis, ws.gctx().cli_unstable().build_analysis) {
    29            (Some(analysis), true) if analysis.enabled => Ok(Some(Self::new(ws)?)),
    30            (Some(_), false) => {
    31                ws.gctx().shell().warn(
    32                    "ignoring 'build.analysis' config, pass `-Zbuild-analysis` to enable it",
    33                )?;
    34                Ok(None)
    35            }
    36            _ => Ok(None),
    37        }
    38    }
    39
    40    fn new(ws: &Workspace<'_>) -> CargoResult<Self> {
    41        let run_id = Self::generate_run_id(ws)?;
    42
    43        let log_dir = ws.gctx().home().join("log");
    44        paths::create_dir_all(log_dir.as_path_unlocked())?;
    45
    46        let filename = format!("{run_id}.jsonl");
    47        let log_file = log_dir.open_rw_exclusive_create(
    48            Path::new(&filename),
    49            ws.gctx(),
    50            "build analysis log",
    51        )?;
    52
    53        let (tx, rx) = mpsc::channel::<LogMessage>();
    54
    55        let run_id_clone = run_id.clone();
    56        let handle = std::thread::spawn(move || {
    57            let mut writer = BufWriter::new(log_file);
    58            for msg in rx {
    59                let _ = msg.write_json_log(&mut writer, &run_id_clone);
    60            }
    61            let _ = writer.flush();
    62        });
    63
    64        Ok(Self {
    65            tx: ManuallyDrop::new(tx),
    66            run_id,
    67            handle: Some(handle),
    68        })
    69    }
    70
    71    /// Generates a unique run ID.
    72    ///
    73    /// The format is `{timestamp}-{hash}`, with `:` and `.` in the timestamp
    74    /// removed to make it safe for filenames.
    75    /// For example, `20251024T194502773638Z-f891d525d52ecab3`.
    76    pub fn generate_run_id(ws: &Workspace<'_>) -> CargoResult<String> {
    77        let hash = short_hash(&ws.root());
    78        let timestamp = jiff::Timestamp::now().to_string().replace([':', '.'], "");
    79        Ok(format!("{timestamp}-{hash}"))
    80    }
    81
    82    /// Returns the run ID for this build session.
    83    pub fn run_id(&self) -> &str {
    84        &self.run_id
    85    }
    86
    87    /// Logs a message.
    88    pub fn log(&self, msg: LogMessage) {
    89        let _ = self.tx.send(msg);
    90    }
    91}
    92
    93impl Drop for BuildLogger {
    94    fn drop(&mut self) {
    95        // SAFETY: tx is dropped exactly once here to signal thread shutdown.
    96        // ManuallyDrop prevents automatic drop after this impl runs.
    97        unsafe {
    98            ManuallyDrop::drop(&mut self.tx);
    99        }
    100
    101        if let Some(handle) = self.handle.take() {
    102            let _ = handle.join();
    103        }
    104    }
    105}