use lazy_static::lazy_static;
use regex::Regex;
use std::{
collections::HashMap as Map,
fs::File,
io::{prelude::*, BufReader},
str::FromStr,
};
lazy_static! {
static ref RE: Regex =
Regex::new(r"(^\$\{?|\$\{)(?P<group_name>\w+)=(?P<pattern>\w+)(\}?$|\})").unwrap();
static ref PATTERNS: Map<&'static str, &'static str> = {
let mut m = Map::new();
m.insert("ip", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}");
m.insert("bridge", r"br-[a-f0-9]{12}");
m
};
}
#[derive(Debug)]
pub struct LogLine {
pub regex: bool,
pub command: String,
pub eval: Option<String>,
}
impl PartialEq for LogLine {
fn eq(&self, other: &LogLine) -> bool {
if self.regex {
if other.regex {
return false;
}
let re = Regex::new(&self.command).unwrap();
if !re.is_match(&other.command) {
return false;
}
if let Some(ref eval) = self.eval {
let captures = re.captures(&other.command).unwrap();
let mut expansion = String::new();
captures.expand(eval, &mut expansion);
let e = eval::eval(&expansion);
e.is_ok() && e.unwrap() == eval::to_value(true)
} else {
true
}
} else if other.regex {
other.eq(self)
} else {
self.command == other.command
}
}
}
impl FromStr for LogLine {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.split('\t').collect::<Vec<_>>();
let (command, expanded) = expand_command(s[0]);
let eval = match s.len() {
1 => None,
2 => Some(s[1].to_owned()),
_ => return Err("string split incorrectly".to_owned()),
};
Ok(LogLine {
command,
regex: expanded,
eval,
})
}
}
fn expand_command(command: &str) -> (String, bool) {
let mut expanded = false;
(
command
.split(' ')
.map(|e| {
if !RE.is_match(e) && RE.find(e).is_none() {
e.to_owned()
} else {
let c = RE.captures(e).unwrap();
let c0 = c.get(0).unwrap();
let (group_name, pattern) = (
c.name("group_name").unwrap().as_str(),
c.name("pattern").unwrap().as_str(),
);
if let Some(pattern) = PATTERNS.get(pattern) {
expanded = true;
let (before, after) = (&e[..c0.start()], &e[c0.end()..]);
format!(r"{}(?P<{}>{}){}", before, group_name, pattern, after)
} else {
e.to_owned()
}
}
})
.collect::<Vec<_>>()
.join(" "),
expanded,
)
}
#[allow(dead_code)]
pub fn load_loglines(log_path: &str) -> Vec<LogLine> {
let file = BufReader::new(File::open(log_path).unwrap());
let mut v: Vec<LogLine> = Vec::new();
for line in file.lines() {
if line.is_err() {
continue;
}
let line = line.unwrap();
v.push(FromStr::from_str(&line).expect("invalid log line"));
}
v
}