use crate::filter::expr;
use crate::filter::expr_filter::ExprFilter;
use crate::record::LogRecord;
use crate::traits::LogFilter;
use tracing::{instrument, warn};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FilterAction {
Exclude,
Include,
}
#[derive(Debug)]
pub struct FilterEntry {
pub action: FilterAction,
pub filter: Box<dyn LogFilter>,
}
#[derive(Debug)]
pub struct FilterEngine {
filters: Vec<FilterEntry>,
}
impl FilterEngine {
pub fn new() -> Self {
Self {
filters: Vec::new(),
}
}
pub fn add_filter(&mut self, action: FilterAction, filter: Box<dyn LogFilter>) {
self.filters.push(FilterEntry { action, filter });
}
#[instrument(skip(self), fields(expression))]
pub fn add_expr_filter(
&mut self,
action: FilterAction,
expression: &str,
) -> Result<(), String> {
let parsed = expr::parse(expression)?;
expr::validate(&parsed)?;
self.add_filter(action, Box::new(ExprFilter::new(parsed, expression)));
Ok(())
}
pub fn clear(&mut self) {
self.filters.clear();
}
pub fn matches(&self, record: &LogRecord) -> bool {
for entry in &self.filters {
if entry.action == FilterAction::Exclude && entry.filter.matches(record) {
return false;
}
}
let has_includes = self
.filters
.iter()
.any(|e| e.action == FilterAction::Include);
if has_includes {
self.filters
.iter()
.any(|e| e.action == FilterAction::Include && e.filter.matches(record))
} else {
true
}
}
#[instrument(skip(self, records), fields(record_count = records.len()))]
pub fn apply(&self, records: &[LogRecord]) -> Vec<usize> {
self.apply_iter(records.iter())
}
pub fn apply_iter<'a>(&self, records: impl Iterator<Item = &'a LogRecord>) -> Vec<usize> {
let excludes: Vec<&dyn LogFilter> = self
.filters
.iter()
.filter(|e| e.action == FilterAction::Exclude)
.map(|e| e.filter.as_ref())
.collect();
let includes: Vec<&dyn LogFilter> = self
.filters
.iter()
.filter(|e| e.action == FilterAction::Include)
.map(|e| e.filter.as_ref())
.collect();
let has_includes = !includes.is_empty();
records
.enumerate()
.filter(|(_, record)| {
if excludes.iter().any(|f| f.matches(record)) {
return false;
}
if has_includes {
includes.iter().any(|f| f.matches(record))
} else {
true
}
})
.map(|(i, _)| i)
.collect()
}
}
impl Default for FilterEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "engine_tests.rs"]
mod engine_tests;