Skip to content

Commit 0cf8bcf

Browse files
committed
feat(build-analysis): add simple logger
The logging infrastructure for `-Zbuild-analysis`. Some highlights: - JSONL logs stored in `~/.cargo/log/`. - Unique log file for each cargo invocation, with a run ID based on workspace hash and timestamp. - Uses background thread for log writes and serialization. Open to use other battle-tested crates in the future.
1 parent 7b7515e commit 0cf8bcf

File tree

5 files changed

+148
-1
lines changed

5 files changed

+148
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ ignore.workspace = true
183183
im-rc.workspace = true
184184
indexmap.workspace = true
185185
itertools.workspace = true
186-
jiff.workspace = true
186+
jiff = { workspace = true, features = ["serde", "std"] }
187187
jobserver.workspace = true
188188
lazycell.workspace = true
189189
libgit2-sys.workspace = true

src/cargo/ops/cargo_compile/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ use crate::core::{PackageId, PackageSet, SourceId, TargetKind, Workspace};
5353
use crate::drop_println;
5454
use crate::ops;
5555
use crate::ops::resolve::{SpecsAndResolvedFeatures, WorkspaceResolve};
56+
use crate::util::BuildLogger;
5657
use crate::util::context::{GlobalContext, WarningHandling};
5758
use crate::util::interning::InternedString;
5859
use crate::util::{CargoResult, StableHasher};
@@ -155,6 +156,8 @@ pub fn compile_ws<'a>(
155156
exec: &Arc<dyn Executor>,
156157
) -> CargoResult<Compilation<'a>> {
157158
let interner = UnitInterner::new();
159+
let _logger = BuildLogger::maybe_new(ws)?;
160+
158161
let bcx = create_bcx(ws, options, &interner)?;
159162
if options.build_config.unit_graph {
160163
unit_graph::emit_serialized_unit_graph(&bcx.roots, &bcx.unit_graph, ws.gctx())?;

src/cargo/util/log_message.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//! Messages for logging.
2+
3+
use std::io::Write;
4+
5+
use jiff::Timestamp;
6+
use serde::Serialize;
7+
8+
/// A log message.
9+
///
10+
/// Each variant represents a different type of event.
11+
#[derive(Serialize)]
12+
#[serde(tag = "reason", rename_all = "kebab-case")]
13+
pub enum LogMessage {}
14+
15+
impl LogMessage {
16+
/// Serializes this message as a JSON log line directly to the writer.
17+
pub fn write_json_log<W: Write>(&self, writer: &mut W, run_id: &str) -> std::io::Result<()> {
18+
#[derive(Serialize)]
19+
struct LogEntry<'a> {
20+
run_id: &'a str,
21+
timestamp: Timestamp,
22+
#[serde(flatten)]
23+
msg: &'a LogMessage,
24+
}
25+
26+
let entry = LogEntry {
27+
run_id,
28+
timestamp: Timestamp::now(),
29+
msg: self,
30+
};
31+
32+
serde_json::to_writer(&mut *writer, &entry)?;
33+
writer.write_all(b"\n")?;
34+
Ok(())
35+
}
36+
}

src/cargo/util/logger.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//! Build analysis logging infrastructure.
2+
3+
use std::io::{BufWriter, Write};
4+
use std::mem::ManuallyDrop;
5+
use std::path::Path;
6+
use std::sync::mpsc;
7+
use std::sync::mpsc::Sender;
8+
use std::thread::JoinHandle;
9+
10+
use cargo_util::paths;
11+
12+
use crate::CargoResult;
13+
use crate::core::Workspace;
14+
use crate::util::log_message::LogMessage;
15+
use crate::util::short_hash;
16+
17+
/// Logger for `-Zbuild-analysis`.
18+
pub struct BuildLogger {
19+
tx: ManuallyDrop<Sender<LogMessage>>,
20+
run_id: String,
21+
handle: Option<JoinHandle<()>>,
22+
}
23+
24+
impl 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+
93+
impl 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+
}

src/cargo/util/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub use self::into_url::IntoUrl;
1818
pub use self::into_url_with_base::IntoUrlWithBase;
1919
pub(crate) use self::io::LimitErrorReader;
2020
pub use self::lockserver::{LockServer, LockServerClient, LockServerStarted};
21+
pub use self::logger::BuildLogger;
2122
pub use self::once::OnceExt;
2223
pub use self::progress::{Progress, ProgressStyle};
2324
pub use self::queue::Queue;
@@ -55,6 +56,8 @@ mod io;
5556
pub mod job;
5657
pub mod lints;
5758
mod lockserver;
59+
pub mod log_message;
60+
mod logger;
5861
pub mod machine_message;
5962
pub mod network;
6063
mod once;

0 commit comments

Comments
 (0)