1use fs_err::File;
5use fs_err::PathExt;
6use futures::AsyncBufReadExt;
7use futures::AsyncRead;
8use futures::AsyncReadExt;
9use futures::StreamExt;
10use futures::io::BufReader;
11use jiff::Timestamp;
12use kmsg::KmsgParsedEntry;
13use parking_lot::Mutex;
14use std::collections::HashMap;
15use std::io::Read;
16use std::io::Write as _;
17use std::path::Path;
18use std::path::PathBuf;
19use std::sync::Arc;
20use tracing::Level;
21use tracing::level_filters::LevelFilter;
22use tracing_subscriber::filter::Targets;
23use tracing_subscriber::fmt::MakeWriter;
24use tracing_subscriber::fmt::format::FmtSpan;
25use tracing_subscriber::layer::SubscriberExt;
26use tracing_subscriber::util::SubscriberInitExt;
27
28#[derive(Clone, Debug)]
30pub struct PetriLogSource(Arc<LogSourceInner>);
31
32#[derive(Debug)]
33struct LogSourceInner {
34 root_path: PathBuf,
35 json_log: JsonLog,
36 log_files: Mutex<HashMap<String, PetriLogFile>>,
37 attachments: Mutex<HashMap<String, u64>>,
38}
39
40impl PetriLogSource {
41 pub fn log_file(&self, name: &str) -> anyhow::Result<PetriLogFile> {
46 use std::collections::hash_map::Entry;
47
48 let mut log_files = self.0.log_files.lock();
49 let log_file = match log_files.entry(name.to_owned()) {
50 Entry::Occupied(occupied_entry) => occupied_entry.get().clone(),
51 Entry::Vacant(vacant_entry) => {
52 let mut path = self.0.root_path.join(name);
53 path.set_extension("log");
57 let file = File::create(&path)?;
58 println!("[[ATTACHMENT|{}]]", path.display());
61 vacant_entry
62 .insert(PetriLogFile(Arc::new(LogFileInner {
63 file,
64 json_log: self.0.json_log.clone(),
65 source: name.to_owned(),
66 })))
67 .clone()
68 }
69 };
70 Ok(log_file)
71 }
72
73 fn attachment_path(&self, name: &str) -> PathBuf {
74 let mut attachments = self.0.attachments.lock();
75 let next = attachments.entry(name.to_owned()).or_default();
76 let name = Path::new(name);
77 let name = if *next == 0 {
78 name
79 } else {
80 let base = name.file_stem().unwrap().to_str().unwrap();
81 let extension = name.extension().unwrap_or_default();
82 &Path::new(&format!("{}_{}", base, *next)).with_extension(extension)
83 };
84 *next += 1;
85 self.0.root_path.join(name)
86 }
87
88 pub fn create_attachment(&self, filename: &str) -> anyhow::Result<File> {
93 let path = self.attachment_path(filename);
94 let file = File::create(&path)?;
95 self.trace_attachment(&path);
96 Ok(file)
97 }
98
99 pub fn write_attachment(&self, filename: &str, mut data: impl Read) -> anyhow::Result<PathBuf> {
104 let path = self.attachment_path(filename);
105 let mut file = File::create(&path)?;
106 std::io::copy(&mut data, &mut file)?;
107 self.trace_attachment(&path);
108 Ok(path)
109 }
110
111 pub fn copy_attachment(
116 &self,
117 attachment_filename: &str,
118 source_path: &Path,
119 ) -> anyhow::Result<()> {
120 let dest_path = self.attachment_path(attachment_filename);
121 fs_err::copy(source_path, &dest_path)?;
122 self.trace_attachment(&dest_path);
123 Ok(())
124 }
125
126 fn trace_attachment(&self, path: &Path) {
127 self.0
129 .json_log
130 .write_attachment(path.file_name().unwrap().as_ref());
131 println!("[[ATTACHMENT|{}]]", path.display());
132 }
133
134 pub fn log_test_start(&self, name: &str) {
142 fs_err::write(self.0.root_path.join("petri.test"), name).unwrap();
143 }
144
145 pub fn log_test_result(&self, r: &anyhow::Result<()>, unstable: bool) {
147 let (result_path, contents) = match &r {
148 Ok(()) => {
149 tracing::info!("test passed");
150 ("petri.passed", String::new())
151 }
152 Err(err) if unstable => {
153 tracing::warn!(
154 error = err.as_ref() as &dyn std::error::Error,
155 "unstable test failed"
156 );
157 ("petri.failed_unstable", format!("{err:#}"))
158 }
159 Err(err) => {
160 tracing::error!(
161 error = err.as_ref() as &dyn std::error::Error,
162 "test failed"
163 );
164 ("petri.failed", format!("{err:#}"))
165 }
166 };
167 fs_err::write(self.0.root_path.join(result_path), contents).unwrap();
171 }
172
173 pub fn output_dir(&self) -> &Path {
175 &self.0.root_path
176 }
177}
178
179#[derive(Clone, Debug)]
180struct JsonLog(Arc<File>);
181
182impl JsonLog {
183 fn write_json(&self, v: &impl serde::Serialize) {
184 let v = serde_json::to_vec(v);
185 if let Ok(mut v) = v {
186 v.push(b'\n');
187 let _ = self.0.as_ref().write_all(&v);
189 }
190 }
191
192 fn write_entry(&self, timestamp: Option<Timestamp>, level: Level, source: &str, buf: &[u8]) {
193 #[derive(serde::Serialize)]
194 struct JsonEntry<'a> {
195 timestamp: Timestamp,
196 source: &'a str,
197 severity: &'a str,
198 message: &'a str,
199 }
200 let message = String::from_utf8_lossy(buf);
201 self.write_json(&JsonEntry {
202 timestamp: timestamp.unwrap_or_else(Timestamp::now),
203 source,
204 severity: level.as_str(),
205 message: message.trim_ascii(),
206 });
207 }
208
209 fn write_attachment(&self, path: &Path) {
210 #[derive(serde::Serialize)]
211 struct JsonEntry<'a> {
212 timestamp: Timestamp,
213 attachment: &'a Path,
214 }
215 self.write_json(&JsonEntry {
216 timestamp: Timestamp::now(),
217 attachment: path,
218 });
219 }
220}
221
222#[derive(Debug)]
223struct LogFileInner {
224 file: File,
225 json_log: JsonLog,
226 source: String,
227}
228
229impl LogFileInner {
230 fn write_stdout(&self, buf: &[u8]) {
231 let mut stdout = std::io::stdout().lock();
232 write!(stdout, "[{:>10}] ", self.source).unwrap();
233 stdout.write_all(buf).unwrap();
234 }
235}
236
237struct LogWriter<'a> {
238 inner: &'a LogFileInner,
239 level: Level,
240 timestamp: Option<Timestamp>,
241}
242
243impl std::io::Write for LogWriter<'_> {
244 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
245 self.inner
247 .json_log
248 .write_entry(self.timestamp, self.level, &self.inner.source, buf);
249 let _ = (&self.inner.file).write_all(buf);
251 self.inner.write_stdout(buf);
253 Ok(buf.len())
254 }
255
256 fn flush(&mut self) -> std::io::Result<()> {
257 Ok(())
258 }
259}
260
261#[derive(Clone, Debug)]
267pub struct PetriLogFile(Arc<LogFileInner>);
268
269impl PetriLogFile {
270 pub fn write_entry_fmt(
272 &self,
273 timestamp: Option<Timestamp>,
274 level: Level,
275 args: std::fmt::Arguments<'_>,
276 ) {
277 let _ = LogWriter {
280 inner: &self.0,
281 level,
282 timestamp,
283 }
284 .write_all(format!("{}\n", args).as_bytes());
285 }
286
287 pub fn write_entry(&self, message: impl std::fmt::Display) {
289 self.write_entry_fmt(None, Level::INFO, format_args!("{}", message));
290 }
291}
292
293#[macro_export]
295macro_rules! log {
296 ($file:expr, $($arg:tt)*) => {
297 <$crate::PetriLogFile>::write_entry_fmt(&$file, format_args!($($arg)*))
298 };
299}
300
301pub fn try_init_tracing(
310 root_path: &Path,
311 default_level: LevelFilter,
312) -> anyhow::Result<PetriLogSource> {
313 let targets =
314 if let Ok(var) = std::env::var("OPENVMM_LOG").or_else(|_| std::env::var("HVLITE_LOG")) {
315 var.parse().unwrap()
316 } else {
317 Targets::new().with_default(default_level)
318 };
319
320 let root_path = root_path.fs_err_canonicalize()?;
322 let jsonl = File::create(root_path.join("petri.jsonl"))?;
323 let logger = PetriLogSource(Arc::new(LogSourceInner {
324 json_log: JsonLog(Arc::new(jsonl)),
325 root_path,
326 log_files: Default::default(),
327 attachments: Default::default(),
328 }));
329
330 let petri_log = logger.log_file("petri")?;
331
332 tracing_subscriber::fmt()
333 .compact()
334 .with_ansi(false) .log_internal_errors(true)
336 .with_writer(PetriWriter(petri_log))
337 .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
338 .with_max_level(LevelFilter::TRACE)
339 .finish()
340 .with(targets)
341 .try_init()?;
342
343 Ok(logger)
344}
345
346struct PetriWriter(PetriLogFile);
347
348impl<'a> MakeWriter<'a> for PetriWriter {
349 type Writer = LogWriter<'a>;
350
351 fn make_writer(&'a self) -> Self::Writer {
352 LogWriter {
353 inner: &self.0.0,
354 level: Level::INFO,
355 timestamp: None,
356 }
357 }
358
359 fn make_writer_for(&'a self, meta: &tracing::Metadata<'_>) -> Self::Writer {
360 LogWriter {
361 inner: &self.0.0,
362 level: *meta.level(),
363 timestamp: None,
364 }
365 }
366}
367
368pub async fn log_task(
373 log_file: PetriLogFile,
374 reader: impl AsyncRead + Unpin + Send + 'static,
375 name: &str,
376) -> anyhow::Result<()> {
377 tracing::info!("connected to {name}");
378 let mut buf = Vec::new();
379 let mut reader = BufReader::new(reader);
380 loop {
381 buf.clear();
382 match (&mut reader).take(256).read_until(b'\n', &mut buf).await {
383 Ok(0) => {
384 tracing::info!("disconnected from {name}: EOF");
385 return Ok(());
386 }
387 Err(e) => {
388 tracing::info!("disconnected from {name}: error: {e:#}");
389 return Err(e.into());
390 }
391 _ => {}
392 }
393
394 let string_buf = String::from_utf8_lossy(&buf);
395 let string_buf_trimmed = string_buf.trim_end();
396
397 if let Some(message) = kmsg::SyslogParsedEntry::new(string_buf_trimmed) {
398 let level = kernel_level_to_tracing_level(message.level);
399 log_file.write_entry_fmt(None, level, format_args!("{}", message.display(false)));
400 } else {
401 log_file.write_entry(string_buf_trimmed);
402 }
403 }
404}
405
406fn kernel_level_to_tracing_level(kernel_level: u8) -> Level {
408 match kernel_level {
409 0..=3 => Level::ERROR,
410 4 => Level::WARN,
411 5..=6 => Level::INFO,
412 7 => Level::DEBUG,
413 _ => Level::INFO,
414 }
415}
416
417pub async fn kmsg_log_task(
419 log_file: PetriLogFile,
420 diag_client: diag_client::DiagClient,
421) -> anyhow::Result<()> {
422 loop {
423 diag_client.wait_for_server().await?;
424 let mut kmsg = diag_client.kmsg(true).await?;
425 tracing::info!("kmsg connected");
426 while let Some(data) = kmsg.next().await {
427 match data {
428 Ok(data) => {
429 let message = KmsgParsedEntry::new(&data).unwrap();
430 let level = kernel_level_to_tracing_level(message.level);
431 log_file.write_entry_fmt(
432 None,
433 level,
434 format_args!("{}", message.display(false)),
435 );
436 }
437 Err(err) => {
438 tracing::info!("kmsg disconnected: {err:#}");
439 break;
440 }
441 }
442 }
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn test_kernel_level_to_tracing_level() {
452 assert_eq!(kernel_level_to_tracing_level(0), Level::ERROR);
454 assert_eq!(kernel_level_to_tracing_level(1), Level::ERROR);
455 assert_eq!(kernel_level_to_tracing_level(2), Level::ERROR);
456 assert_eq!(kernel_level_to_tracing_level(3), Level::ERROR);
457
458 assert_eq!(kernel_level_to_tracing_level(4), Level::WARN);
460
461 assert_eq!(kernel_level_to_tracing_level(5), Level::INFO);
463 assert_eq!(kernel_level_to_tracing_level(6), Level::INFO);
464
465 assert_eq!(kernel_level_to_tracing_level(7), Level::DEBUG);
467
468 assert_eq!(kernel_level_to_tracing_level(8), Level::INFO);
470 assert_eq!(kernel_level_to_tracing_level(255), Level::INFO);
471 }
472}