From 6436e3f96c837ee17b9fcb07f7e068b965cec913 Mon Sep 17 00:00:00 2001 From: vToMy Date: Wed, 11 Sep 2024 14:46:24 +0300 Subject: [PATCH] Added support for outputting to stdout when no output is given. Changed prints to print to stderr, so it won't get mixed with stdout. Parse arguments only once in main and pass them along. Construct writer once in main and pass it along. Flush csv (whether outputting to file or stdout) after every input file. --- examples/unifiedlog_parser/src/main.rs | 98 ++++++++++++++------------ 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/examples/unifiedlog_parser/src/main.rs b/examples/unifiedlog_parser/src/main.rs index 6e827c2..b0d0a5c 100755 --- a/examples/unifiedlog_parser/src/main.rs +++ b/examples/unifiedlog_parser/src/main.rs @@ -17,11 +17,13 @@ use macos_unifiedlogs::unified_log::{LogData, UnifiedLogData}; use macos_unifiedlogs::uuidtext::UUIDText; use simplelog::{Config, SimpleLogger}; use std::error::Error; -use std::fs; +use std::{fs, io}; use std::fs::OpenOptions; +use std::io::Write; use std::path::PathBuf; use clap::Parser; +use csv::Writer; #[derive(Parser, Debug)] #[clap(version, about, long_about = None)] @@ -35,29 +37,30 @@ struct Args { input: String, /// Path to output file. Any directories must already exist - #[clap(short, long)] + #[clap(short, long, default_value = "")] output: String, } fn main() { - println!("Starting Unified Log parser..."); + eprintln!("Starting Unified Log parser..."); // Set logging level to warning SimpleLogger::init(LevelFilter::Warn, Config::default()) .expect("Failed to initialize simple logger"); let args = Args::parse(); + let mut writer = construct_writer(&args.output).unwrap(); // Create headers for CSV file - output_header().unwrap(); + output_header(&mut writer).unwrap(); - if args.input != "" && args.output != "" { - parse_log_archive(&args.input); + if args.input != "" { + parse_log_archive(&args.input, &mut writer); } else if args.live != "false" { - parse_live_system(); + parse_live_system(&mut writer); } } -// Parse a provided directory path. Currently expect the path to follow macOS log collect structure -fn parse_log_archive(path: &str) { +// Parse a provided directory path. Currently, expect the path to follow macOS log collect structure +fn parse_log_archive(path: &str, writer: &mut Writer>) { let mut archive_path = PathBuf::from(path); // Parse all UUID files which contain strings and other metadata @@ -81,13 +84,14 @@ fn parse_log_archive(path: &str) { &shared_strings_results, ×ync_data, path, + writer, ); - println!("\nFinished parsing Unified Log data. Saved results to: output.csv"); + eprintln!("\nFinished parsing Unified Log data."); } // Parse a live macOS system -fn parse_live_system() { +fn parse_live_system(writer: &mut Writer>) { let strings = collect_strings_system().unwrap(); let shared_strings = collect_shared_strings_system().unwrap(); let timesync_data = collect_timesync_system().unwrap(); @@ -97,18 +101,20 @@ fn parse_live_system() { &shared_strings, ×ync_data, "/private/var/db/diagnostics", + writer ); - println!("\nFinished parsing Unified Log data. Saved results to: output.csv"); + eprintln!("\nFinished parsing Unified Log data."); } // Use the provided strings, shared strings, timesync data to parse the Unified Log data at provided path. -// Currently expect the path to follow macOS log collect structure +// Currently, expect the path to follow macOS log collect structure fn parse_trace_file( string_results: &[UUIDText], shared_strings_results: &[SharedCacheStrings], timesync_data: &[TimesyncBoot], path: &str, + writer: &mut Writer> ) { // We need to persist the Oversize log entries (they contain large strings that don't fit in normal log entries) // Some log entries have Oversize strings located in different tracev3 files. @@ -135,12 +141,12 @@ fn parse_trace_file( for log_path in paths { let data = log_path.unwrap(); let full_path = data.path().display().to_string(); - println!("Parsing: {}", full_path); + eprintln!("Parsing: {}", full_path); let log_data = if data.path().exists() { parse_log(&full_path).unwrap() } else { - println!("File {} no longer on disk", full_path); + eprintln!("File {} no longer on disk", full_path); continue; }; @@ -160,7 +166,7 @@ fn parse_trace_file( // Track missing logs missing_data.push(missing_logs); log_count += results.len(); - output(&results).unwrap(); + output(&results, writer).unwrap(); } } @@ -174,12 +180,12 @@ fn parse_trace_file( for log_path in paths { let data = log_path.unwrap(); let full_path = data.path().display().to_string(); - println!("Parsing: {}", full_path); + eprintln!("Parsing: {}", full_path); let mut log_data = if data.path().exists() { parse_log(&full_path).unwrap() } else { - println!("File {} no longer on disk", full_path); + eprintln!("File {} no longer on disk", full_path); continue; }; @@ -198,7 +204,7 @@ fn parse_trace_file( missing_data.push(missing_logs); log_count += results.len(); - output(&results).unwrap(); + output(&results, writer).unwrap(); } } @@ -212,12 +218,12 @@ fn parse_trace_file( for log_path in paths { let data = log_path.unwrap(); let full_path = data.path().display().to_string(); - println!("Parsing: {}", full_path); + eprintln!("Parsing: {}", full_path); let mut log_data = if data.path().exists() { parse_log(&full_path).unwrap() } else { - println!("File {} no longer on disk", full_path); + eprintln!("File {} no longer on disk", full_path); continue; }; @@ -237,7 +243,7 @@ fn parse_trace_file( missing_data.push(missing_logs); log_count += results.len(); - output(&results).unwrap(); + output(&results, writer).unwrap(); } } archive_path.pop(); @@ -250,12 +256,12 @@ fn parse_trace_file( for log_path in paths { let data = log_path.unwrap(); let full_path = data.path().display().to_string(); - println!("Parsing: {}", full_path); + eprintln!("Parsing: {}", full_path); let mut log_data = if data.path().exists() { parse_log(&full_path).unwrap() } else { - println!("File {} no longer on disk", full_path); + eprintln!("File {} no longer on disk", full_path); continue; }; @@ -275,7 +281,7 @@ fn parse_trace_file( missing_data.push(missing_logs); log_count += results.len(); - output(&results).unwrap(); + output(&results, writer).unwrap(); } } archive_path.pop(); @@ -284,7 +290,7 @@ fn parse_trace_file( // Check if livedata exists. We only have it if 'log collect' was used if archive_path.exists() { - println!("Parsing: logdata.LiveData.tracev3"); + eprintln!("Parsing: logdata.LiveData.tracev3"); let mut log_data = parse_log(&archive_path.display().to_string()).unwrap(); log_data.oversize.append(&mut oversize_strings.oversize); let (results, missing_logs) = build_log( @@ -298,7 +304,7 @@ fn parse_trace_file( missing_data.push(missing_logs); log_count += results.len(); - output(&results).unwrap(); + output(&results, writer).unwrap(); // Track oversize entries oversize_strings.oversize = log_data.oversize; archive_path.pop(); @@ -325,21 +331,25 @@ fn parse_trace_file( ); log_count += results.len(); - output(&results).unwrap(); + output(&results, writer).unwrap(); } - println!("Parsed {} log entries", log_count); + eprintln!("Parsed {} log entries", log_count); } -// Create csv file and create headers -fn output_header() -> Result<(), Box> { - let args = Args::parse(); - - let csv_file = OpenOptions::new() - .append(true) - .create(true) - .open(args.output)?; - let mut writer = csv::Writer::from_writer(csv_file); +fn construct_writer(output_path: &str) -> Result>, Box> { + let writer = if output_path != "" { + Box::new(OpenOptions::new() + .append(true) + .create(true) + .open(output_path)?) as Box + } else { + Box::new(io::stdout()) as Box + }; + Ok(Writer::from_writer(writer)) +} +// Create csv file and create headers +fn output_header(writer: &mut Writer>) -> Result<(), Box> { writer.write_record(&[ "Timestamp", "Event Type", @@ -359,19 +369,12 @@ fn output_header() -> Result<(), Box> { "Boot UUID", "System Timezone Name", ])?; + writer.flush()?; Ok(()) } // Append or create csv file -fn output(results: &Vec) -> Result<(), Box> { - let args = Args::parse(); - - let csv_file = OpenOptions::new() - .append(true) - .create(true) - .open(args.output)?; - let mut writer = csv::Writer::from_writer(csv_file); - +fn output(results: &Vec, writer: &mut Writer>) -> Result<(), Box> { for data in results { let date_time = Utc.timestamp_nanos(data.time as i64); writer.write_record(&[ @@ -394,5 +397,6 @@ fn output(results: &Vec) -> Result<(), Box> { data.timezone_name.to_owned(), ])?; } + writer.flush()?; Ok(()) }