use std::io::Read;
fn level_name(lvl: u64) -> &'static str {
match lvl {
0 => "TRACE",
1 => "DEBUG",
2 => "INFO ",
3 => "WARN ",
4 => "ERROR",
_ => "?????",
}
}
fn level_from_arg(s: &str) -> Option<u64> {
match s.to_ascii_uppercase().as_str() {
"TRACE" => Some(0),
"DEBUG" => Some(1),
"INFO" => Some(2),
"WARN" | "WARNING" => Some(3),
"ERROR" => Some(4),
_ => s.parse::<u64>().ok().filter(|n| *n <= 4),
}
}
fn eagle_display(osc: i64) -> String {
vsf::types::EagleTime::from_oscillations(osc)
.to_datetime()
.format("%Y-%m-%d %H:%M:%S%.3f")
.to_string()
}
struct Filter {
min_level: u64,
grep: Option<String>,
}
fn print_records(buf: &[u8], filter: &Filter) -> usize {
let (records, consumed) = vsf::parse_log_records(buf);
for r in &records {
let pass_level = r.level >= filter.min_level;
let pass_grep = match &filter.grep {
Some(g) => r.msg.to_lowercase().contains(g),
None => true,
};
if pass_level && pass_grep {
let ts = if r.osc != 0 { eagle_display(r.osc) } else { "(no time)".to_string() };
println!("{ts} [{}] {}", level_name(r.level), r.msg);
}
}
consumed
}
fn read_all(path: &str) -> std::io::Result<Vec<u8>> {
let mut bytes = Vec::new();
std::fs::File::open(path)?.read_to_end(&mut bytes)?;
Ok(bytes)
}
fn main() {
let mut path: Option<String> = None;
let mut min_level = 0u64;
let mut grep: Option<String> = None;
let mut follow = false;
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
match a.as_str() {
"-f" | "--follow" => follow = true,
"-l" | "--level" => match args.next().as_deref().and_then(level_from_arg) {
Some(l) => min_level = l,
None => {
eprintln!("vsflog: --level needs TRACE|DEBUG|INFO|WARN|ERROR or 0..4");
std::process::exit(2);
}
},
"-g" | "--grep" => match args.next() {
Some(g) => grep = Some(g.to_lowercase()),
None => {
eprintln!("vsflog: --grep needs a substring");
std::process::exit(2);
}
},
"-h" | "--help" => {
eprintln!(
"vsflog [PATH] [-l LEVEL] [-g SUBSTR] [-f]\n \
PATH defaults to ./verichrome.log.vsf"
);
return;
}
other if !other.starts_with('-') && path.is_none() => path = Some(other.to_string()),
other => {
eprintln!("vsflog: unrecognized argument '{other}'");
std::process::exit(2);
}
}
}
let path = path.unwrap_or_else(|| "verichrome.log.vsf".to_string());
let filter = Filter { min_level, grep };
let bytes = match read_all(&path) {
Ok(b) => b,
Err(e) => {
eprintln!("vsflog: cannot read {path}: {e}");
std::process::exit(1);
}
};
let mut consumed = print_records(&bytes, &filter);
if follow {
use std::io::{Seek, SeekFrom};
loop {
std::thread::sleep(std::time::Duration::from_millis(500));
let Ok(mut f) = std::fs::File::open(&path) else { continue };
let len = f.metadata().map(|m| m.len()).unwrap_or(0);
if len < consumed as u64 {
consumed = 0;
}
if len <= consumed as u64 {
continue;
}
if f.seek(SeekFrom::Start(consumed as u64)).is_err() {
continue;
}
let mut tail = Vec::new();
if f.read_to_end(&mut tail).is_err() {
continue;
}
consumed += print_records(&tail, &filter);
}
}
}