use clap::Parser;
use memmap2::MmapOptions;
use regex::Regex;
use std::fs::File;
use std::time::Instant;
use timber_rs::analyzer::LogAnalyzer;
use timber_rs::cli::Args;
use timber_rs::formatter::print_results;
const PARALLEL_THRESHOLD_BYTES: u64 = 10 * 1024 * 1024;
fn main() -> std::io::Result<()> {
let args = Args::parse();
if !args.json && !args.count {
println!("\nWaking LumberJacks...Timber is chopping: {}\n", args.file);
}
let pattern = match &args.chop {
Some(pattern) => {
if !args.json && !args.count {
println!("Searching for pattern: {}", pattern);
}
Some(Regex::new(pattern).expect("Invalid regex pattern"))
}
None => None,
};
let level = args.level.as_deref();
if let Some(level_str) = level {
if !args.json && !args.count {
println!("Filtering by level: {}", level_str);
}
}
let start_time = Instant::now();
let mut analyzer = LogAnalyzer::new();
let use_parallel = if args.sequential {
false
} else if args.parallel {
true
} else {
should_use_parallel(&args.file)
};
if args.count {
let count = count_total_logs(&args.file, pattern.as_ref(), level)?;
println!("{}", count);
return Ok(());
}
let result = process_with_mmap(
&args.file,
&mut analyzer,
pattern.as_ref(),
level,
args.trend,
args.stats,
use_parallel,
)?;
let elapsed = start_time.elapsed();
if !args.json {
println!("Analysis completed in {:.2}s", elapsed.as_secs_f32());
}
if !args.json {
println!();
}
print_results(
&result,
args.trend,
args.stats,
args.json,
args.top_errors,
args.show_unique,
);
Ok(())
}
fn count_total_logs(
file_path: &str,
pattern: Option<&Regex>,
level_filter: Option<&str>,
) -> std::io::Result<usize> {
let file = File::open(file_path)?;
let mmap = unsafe { MmapOptions::new().map(&file)? };
let mut total_count = 0;
let mut analyzer = LogAnalyzer::new();
if let Some(pat) = pattern {
analyzer.configure(Some(&pat.to_string()), level_filter);
} else {
analyzer.configure(None, level_filter);
}
const FAST_CHUNK_SIZE: usize = 1_048_576; let mut position = 0;
while position < mmap.len() {
let chunk_end = std::cmp::min(position + FAST_CHUNK_SIZE, mmap.len());
let chunk = &mmap[position..chunk_end];
let mut result = timber_rs::analyzer::AnalysisResult::default();
analyzer.process_chunk_data(chunk, &mut result, false, false);
total_count += result.count;
position += chunk_end - position;
}
Ok(total_count)
}
fn process_with_mmap(
file_path: &str,
analyzer: &mut LogAnalyzer,
pattern: Option<&Regex>,
level_filter: Option<&str>,
collect_trends: bool,
collect_stats: bool,
use_parallel: bool,
) -> std::io::Result<timber_rs::analyzer::AnalysisResult> {
let path = std::path::Path::new(file_path);
if !path.exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("File not found: {}", file_path),
));
}
let file = File::open(path)?;
let file_size = file.metadata()?.len();
if file_size == 0 {
return Ok(timber_rs::analyzer::AnalysisResult::default());
}
let mmap = unsafe { MmapOptions::new().map(&file)? };
if use_parallel && file_size > PARALLEL_THRESHOLD_BYTES {
Ok(analyzer.analyze_mmap_parallel(
&mmap,
pattern,
level_filter,
collect_trends,
collect_stats,
))
} else {
Ok(analyzer.analyze_mmap(&mmap, pattern, level_filter, collect_trends, collect_stats))
}
}
fn should_use_parallel(file_path: &str) -> bool {
match std::fs::metadata(file_path) {
Ok(metadata) => {
let size = metadata.len();
size > PARALLEL_THRESHOLD_BYTES
}
Err(_) => false, }
}