#[cfg(test)]
#[macro_use]
extern crate slog;
#[cfg(not(test))]
extern crate slog;
extern crate regex;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::option::Option;
use std::panic::UnwindSafe;
use std::panic::RefUnwindSafe;
use std::fmt::format;
use slog::KV;
use regex::Regex;
struct FilteringSerializer<'a> {
pending_matches: KVFilterListFlyWeight<'a>,
tmp_str: String,
}
impl<'a> slog::Serializer for FilteringSerializer<'a> {
fn emit_arguments(&mut self, key: slog::Key, val: &fmt::Arguments) -> slog::Result {
if self.pending_matches.is_empty() {
return Ok(());
}
let matched = if let Some(keyvalues) = self.pending_matches.get(&key) {
self.tmp_str.clear();
fmt::write(&mut self.tmp_str, *val)?;
keyvalues.contains(&self.tmp_str)
} else {
false
};
if matched {
self.pending_matches.remove(&key);
}
Ok(())
}
}
pub type KVFilterList = HashMap<String, HashSet<String>>;
type KVFilterListFlyWeight<'a> = HashMap<&'a str, &'a HashSet<String>>;
pub struct KVFilter<D: slog::Drain> {
drain: D,
filters: Option<KVFilterList>,
neg_filters: Option<KVFilterList>,
level: slog::Level,
regex: Option<Regex>,
neg_regex: Option<Regex>,
}
impl<D: slog::Drain> UnwindSafe for KVFilter<D> {}
impl<D: slog::Drain> RefUnwindSafe for KVFilter<D> {}
impl<'a, D: slog::Drain> KVFilter<D> {
pub fn new(drain: D, level: slog::Level) -> Self {
KVFilter {
drain: drain,
level: level,
filters: None,
neg_filters: None,
regex: None,
neg_regex: None,
}
}
pub fn only_pass_any_on_all_keys(mut self, filters: Option<KVFilterList>) -> Self {
self.filters = filters;
self
}
pub fn always_suppress_any(mut self, filters: Option<KVFilterList>) -> Self {
self.neg_filters = filters;
self
}
pub fn only_pass_on_regex(mut self, regex: Option<Regex>) -> Self {
self.regex = regex;
self
}
pub fn always_suppress_on_regex(mut self, regex: Option<Regex>) -> Self {
self.neg_regex = regex;
self
}
fn is_match(&self, record: &slog::Record, logger_values: &slog::OwnedKVList) -> bool {
let mut ser = FilteringSerializer {
pending_matches: self.filters.as_ref().map_or(HashMap::new(), |f| {
f.iter().map(|(k, v)| (k.as_str(), v)).collect()
}),
tmp_str: String::new(),
};
let mut negser = FilteringSerializer {
pending_matches: self.neg_filters.as_ref().map_or(HashMap::new(), |ref f| {
f.iter().map(|(k, v)| (k.as_str(), v)).collect()
}),
tmp_str: String::new(),
};
record.kv().serialize(record, &mut ser).unwrap();
record.kv().serialize(record, &mut negser).unwrap();
logger_values.serialize(record, &mut negser).unwrap();
let anynegativematch = ||
negser.pending_matches.len() == self.neg_filters.as_ref()
.map_or(0,
|m| m.keys().len());
let mut pass = if ser.pending_matches.is_empty() {
anynegativematch()
} else {
logger_values.serialize(record, &mut ser).unwrap();
if ser.pending_matches.is_empty() {
anynegativematch()
} else {
false
}
};
if pass && (self.regex.is_some() || self.neg_regex.is_some()) {
let res = format(*record.msg());
if let Some(ref posmatch) = self.regex {
pass = posmatch.is_match(&res);
};
if pass {
if let Some(ref negmatch) = self.neg_regex {
pass = !negmatch.is_match(&res);
}
}
}
pass
}
}
impl<'a, D: slog::Drain> slog::Drain for KVFilter<D> {
type Err = D::Err;
type Ok = Option<D::Ok>;
fn log(
&self,
info: &slog::Record,
logger_values: &slog::OwnedKVList,
) -> Result<Self::Ok, Self::Err> {
if info.level() < self.level || self.is_match(info, logger_values) {
self.drain.log(info, logger_values).map(Some)
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use super::KVFilter;
use slog::{Drain, Level, Logger, OwnedKVList, Record};
use regex::Regex;
use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Mutex;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::io;
use std::sync::Arc;
const YES: &'static str = "YES";
const NO: &'static str = "NO";
#[derive(Debug)]
struct StringDrain {
output: Arc<Mutex<Vec<String>>>,
}
impl<'a> Drain for StringDrain {
type Err = io::Error;
type Ok = ();
fn log(&self, info: &Record, _: &OwnedKVList) -> io::Result<()> {
let mut lo = self.output.lock().unwrap();
let fmt = format!("{:?}", info.msg());
if !fmt.contains(YES) && !fmt.contains(NO) {
panic!(fmt);
}
(*lo).push(fmt);
Ok(())
}
}
impl<'a> Display for StringDrain {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "none")
}
}
fn testkvfilter<D: Drain>(d: D) -> KVFilter<D> {
KVFilter::new(d, Level::Info).only_pass_any_on_all_keys(Some(
vec![
(
"thread".to_string(),
HashSet::from_iter(vec!["100".to_string(), "200".to_string()]),
),
(
"direction".to_string(),
HashSet::from_iter(vec!["send".to_string(), "receive".to_string()]),
),
].into_iter()
.collect(),
))
}
fn testnegkvfilter<D: Drain>(f: KVFilter<D>) -> KVFilter<D> {
f.always_suppress_any(Some(
vec![
(
"deepcomp".to_string(),
HashSet::from_iter(vec!["1".to_string(), "2".to_string()]),
),
(
"deepercomp".to_string(),
HashSet::from_iter(vec!["4".to_string(), "5".to_string()]),
),
].into_iter()
.collect(),
))
}
#[test]
fn nodecomponentlogfilter() {
assert!(Level::Critical < Level::Warning);
let out = Arc::new(Mutex::new(vec![]));
let drain = StringDrain {
output: out.clone(),
};
let filter = testkvfilter(drain);
let mainlog = Logger::root(filter.fuse(), o!("version" => env!("CARGO_PKG_VERSION")));
let sublog = mainlog.new(o!("thread" => "200", "sub" => "sub"));
let subsublog = sublog.new(o!("direction" => "send"));
let subsubsublog = subsublog.new(o!());
let wrongthread = mainlog.new(o!("thread" => "400", "sub" => "sub"));
info!(mainlog, "NO: filtered, main, no keys");
info!(mainlog, "YES: unfiltered, on of thread matches, direction matches";
"thread" => "100", "direction" => "send");
info!(mainlog,
"YES: unfiltered, on of thread matches, direction matches, different key order";
"direction" => "send", "thread" => "100");
warn!(mainlog, "YES: unfiltered, higher level");
debug!(mainlog, "NO: filtered, level to low, no keys");
info!(mainlog, "NO: filtered, wrong thread on record";
"thread" => "300", "direction" => "send");
info!(wrongthread, "NO: filtered, wrong thread on sublog");
info!(sublog, "NO: filtered sublog, missing dirction ");
info!(sublog, "YES: unfiltered sublog with added directoin";
"direction" => "receive");
info!(
subsubsublog,
"YES: unfiltered subsubsublog, direction on subsublog, thread on sublog"
);
let stackedthreadslog = wrongthread.new(o!("thread" => "200"));
info!(stackedthreadslog,
"YES: unfiltered since one of the threads matches from inherited";
"direction" => "send");
println!("resulting output: {:#?}", *out.lock().unwrap());
assert_eq!(out.lock().unwrap().len(), 6);
}
#[test]
fn negnodecomponentlogfilter() {
assert!(Level::Critical < Level::Warning);
let out = Arc::new(Mutex::new(vec![]));
let drain = StringDrain {
output: out.clone(),
};
let filter = testnegkvfilter(testkvfilter(drain.fuse()));
let mainlog = Logger::root(filter.fuse(), o!("version" => env!("CARGO_PKG_VERSION")));
let sublog = mainlog.new(o!("thread" => "200", "sub" => "sub"));
let subsublog = sublog.new(o!("direction" => "send"));
let subsubsublog = subsublog.new(o!("deepcomp" => "0"));
let negsubsubsublog = subsublog.new(o!("deepcomp" => "1"));
info!(mainlog, "NO: filtered, main, no keys");
info!(mainlog, "YES: unfiltered, on of thread matches, direction matches";
"thread" => "100", "direction" => "send");
info!(subsubsublog, "YES: unfiltered, on of thread matches, direction matches, deep doesn't apply";
"thread" => "100", "direction" => "send");
info!(negsubsubsublog, "NO: filtered, on of thread matches, direction matches, deep negative applies";
"thread" => "100", "direction" => "send");
info!(subsubsublog, "NO: filtered, on of thread matches, direction matches, deep doesn't apply but deeper does";
"thread" => "100", "direction" => "send", "deepercomp" => "4");
info!(subsubsublog, "YES: unfiltered, on of thread matches, direction matches, deep doesn't apply and deeper doesn't";
"thread" => "100", "direction" => "send", "deepercomp" => "7");
println!("resulting output: {:#?}", *out.lock().unwrap());
assert_eq!(out.lock().unwrap().len(), 3);
}
#[test]
fn regextest() {
assert!(Level::Critical < Level::Warning);
let out = Arc::new(Mutex::new(vec![]));
let drain = StringDrain {
output: out.clone(),
};
let filter = KVFilter::new(drain.fuse(), Level::Info)
.only_pass_on_regex(Some(Regex::new(r"PASS\d:").unwrap()))
.always_suppress_on_regex(Some(Regex::new(r"NOPE\d:").unwrap()));
let mainlog = Logger::root(filter.fuse(), o!("version" => env!("CARGO_PKG_VERSION")));
info!(mainlog, "NO: filtered, no positive");
info!(mainlog, "NO: NOPE2 PASS0 filtered, negative");
info!(mainlog, "NO: filtered, no positive");
info!(mainlog, "YES: PASS2: not filtered, positive");
info!(mainlog, "YES: {}: not filtered, positive", "PASS4");
println!("resulting output: {:#?}", *out.lock().unwrap());
assert_eq!(out.lock().unwrap().len(), 2);
}
}