1use 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
17pub struct BuildLogger {
19 tx: ManuallyDrop<Sender<LogMessage>>,
20 run_id: String,
21 handle: Option<JoinHandle<()>>,
22}
23
24impl BuildLogger {
25 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 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 pub fn run_id(&self) -> &str {
84 &self.run_id
85 }
86
87 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 unsafe {
98 ManuallyDrop::drop(&mut self.tx);
99 }
100
101 if let Some(handle) = self.handle.take() {
102 let _ = handle.join();
103 }
104 }
105}