Mercurial > lbo > hg > syslog
changeset 17:4caa98d804bd draft
Implement filter rules
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 03 Dec 2016 14:17:18 +0100 |
parents | 4185ac5ed03d |
children | 08d04d44f86d |
files | src/config.rs |
diffstat | 1 files changed, 245 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/src/config.rs Sat Dec 03 14:17:02 2016 +0100 +++ b/src/config.rs Sat Dec 03 14:17:18 2016 +0100 @@ -1,24 +1,98 @@ //! See prototype.toml for a full example config file. //! +use std::collections::HashMap; +use std::collections::btree_map::Entry; +use std::default::Default; use std::net::SocketAddr; +use dns_lookup; +use toml; + +use priority; + +// helper functions for extracting values from TOML values +fn get_string(tbl: &toml::Table, key: &str, default: String) -> String { + if let Some(e) = tbl.get(key) { + if let &toml::Value::String(ref s) = e { + return s.clone(); + } + } + default +} + +fn get_int(tbl: &toml::Table, key: &str, default: i64) -> i64 { + if let Some(e) = tbl.get(key) { + if let &toml::Value::Integer(i) = e { + return i; + } + } + default +} + +fn tomlerr<T>(s: String) -> Result<T, toml::Error> { + Err(toml::Error::Custom(s)) +} + +// config structs + #[derive(Clone,Default)] pub struct General { pub bind_path: String, pub max_msg_len: usize, } +fn assemble_general(t: &toml::Table) -> Result<General, toml::Error> { + let default_bind_path = "/dev/log".to_string(); + let default_max_msg_len = 8192; + + let mut g = General { + bind_path: get_string(t, "bind_path", default_bind_path), + max_msg_len: get_int(t, "max_msg_len", default_max_msg_len) as usize, + }; + + Ok(g) +} + #[derive(Clone)] pub struct Remote { - addr: SocketAddr + addr: SocketAddr, +} + +/// Assemble a Remote from a `remote` entry, and resolve the address if needed. +fn assemble_remote(t: &toml::Table, name: &str) -> Result<Remote, toml::Error> { + let addr = get_string(t, "addr", String::new()); + let port = get_int(t, "port", 0); + + if !addr.is_empty() { + if let Ok(mut ips) = dns_lookup::lookup_host(&addr) { + if let Some(Ok(ip)) = ips.next() { + return Ok(Remote { addr: SocketAddr::new(ip, port as u16) }); + } + } + } + + Err(toml::Error::Custom(format!("Couldn't parse/resolve host {} for {}", addr, name))) } #[derive(Clone)] pub enum CompressType { NoCompression, - // NOTE: use flate2 for compression. - Gzip + // TODO: implement flate2 for compression. + Gzip, +} + +fn assemble_compress_type(v: &toml::Value) -> Result<CompressType, toml::Error> { + if let &toml::Value::String(ref s) = v { + if s == "none" { + return Ok(CompressType::NoCompression); + } else if s == "gzip" { + return Ok(CompressType::Gzip); + } else { + return Err(toml::Error::Custom(format!("Unknown compression type {}", s))); + } + } + Err(toml::Error::Custom("Expected string for compression type".to_string())) } impl Default for CompressType { @@ -39,27 +113,187 @@ compress: CompressType, } -#[derive(Clone)] -pub enum Pattern { + + +#[derive(Clone, Debug)] +pub enum FacPattern { /// '*' Wildcard, + Facility(priority::Facility), + MultiFacility(Vec<priority::Facility>), +} + +#[derive(Clone, Debug)] +pub enum LvlPattern { + Wildcard, + /// Matches that level, or any lower (more important) level + Level(priority::Level), + /// Matches only that level + ExactLevel(priority::Level), + MultiLevel(Vec<priority::Level>), +} + +impl Default for FacPattern { + fn default() -> FacPattern { + FacPattern::Wildcard + } +} + +impl Default for LvlPattern { + fn default() -> LvlPattern { + LvlPattern::Wildcard + } +} + +/// A Filter specifies one or more Facilities, and one or more Levels. +type Filter = (FacPattern, LvlPattern); +/// A Matcher is one or more Filters. +type Matcher = Vec<Filter>; + +/// a pattern for a rule matches syslog messages based on level and facility. Valid patterns +/// are: +/// +/// *.* -- match all +/// mail.* -- match all mail records +/// mail,news.* -- match all mail and news records +/// mail.warn,info -- match all mail records at warn or info +/// mail,news.info,warn -- match all mail or news records at warn or info. +/// mail.*;news,auth.info -- match all mail records and records for news or auth at info or higher +/// (!) => if only a single level is given, this means "this level or more important"; if you only want to +/// match that single level, use a rule like "mail.=info". +fn parse_matcher(s: &str) -> Result<Matcher, toml::Error> { + let mut matcher = Vec::with_capacity(3); + + for filter in s.trim().split(';') { + match parse_filter(filter) { + Ok(f) => matcher.push(f), + Err(e) => return Err(e), + } + } + Ok(matcher) } -impl Default for Pattern { - fn default() -> Pattern { - Pattern::Wildcard +fn parse_filter(s: &str) -> Result<Filter, toml::Error> { + let mut parts = s.trim().split('.'); + + if let Some(facilities) = parts.next() { + if let Some(levels) = parts.next() { + let fac_pattern = parse_facility_pattern(facilities); + let lvl_pattern = parse_level_pattern(levels); + + return match (parse_facility_pattern(facilities), parse_level_pattern(levels)) { + (Ok(f), Ok(l)) => Ok((f, l)), + (Err(e), Err(f)) => tomlerr(format!("{} {}", e, f)), + (Ok(_), Err(e)) => tomlerr(format!("{}", e)), + (Err(e), Ok(_)) => tomlerr(format!("{}", e)), + }; + } + } + tomlerr(format!("Bad filter pattern: {}", s)) +} + +fn parse_facility_pattern(s: &str) -> Result<FacPattern, toml::Error> { + let mut list: Vec<String> = s.trim().split(',').map(String::from).collect(); + + if list.len() > 1 { + let mut facilities = vec![priority::Facility::LOCAL0; list.len()]; + let mut i = 0; + + for f in list { + if let Some(fac) = priority::str_to_facility(f.trim()) { + facilities[i] = fac; + } else { + return tomlerr(format!("Unrecognized facility: {}", f)); + } + } + Ok(FacPattern::MultiFacility(facilities)) + } else if list.len() == 1 { + if let Some(fac) = priority::str_to_facility(&list[0]) { + Ok(FacPattern::Facility(fac)) + } else { + tomlerr(format!("Unknown facility: {}", &list[0])) + } + } else { + return tomlerr(format!("Level pattern can't be empty")); + } +} + +fn parse_level_pattern(s: &str) -> Result<LvlPattern, toml::Error> { + let mut list: Vec<String> = s.trim().split(',').map(String::from).collect(); + + if list.len() > 1 { + let mut levels = vec![priority::Level::NONE; list.len()]; + let mut i = 0; + + for l in list { + if let Some(lvl) = priority::str_to_level(l.trim()) { + levels[i] = lvl; + } else { + return tomlerr(format!("Unrecognized facility: {}", l)); + } + } + Ok(LvlPattern::MultiLevel(levels)) + } else if list.len() == 1 { + let l = &list[0]; + + // exact rule + if &l[0..1] == "=" { + if let Some(fac) = priority::str_to_level(&l[1..]) { + Ok(LvlPattern::ExactLevel(fac)) + } else { + tomlerr(format!("Unknown exact facility: {}", l)) + } + } else if let Some(lvl) = priority::str_to_level(l) { + Ok(LvlPattern::Level(lvl)) + } else { + tomlerr(format!("Unknown facility: {}", &list[0])) + } + } else { + return tomlerr(format!("Level pattern can't be empty")); } } #[derive(Clone,Default)] pub struct Rule { - level_pattern: Pattern, - // referes to File.name - dest: String, + pattern: Matcher, + // refers to the file entry name, not the file name. + file: String, + remote_dest: String, + stop: bool, } #[derive(Clone,Default)] pub struct Config { pub general: General, + remotes: HashMap<String, Remote>, + files: HashMap<String, File>, + rules: Vec<Rule>, +} + +fn decode_config(text: &String) -> Result<Config, toml::Error> { + if let Some(tbl) = toml::Parser::new(&text).parse() { + let general = try!(assemble_general(&tbl)); + + unimplemented!() + } else { + return Err(toml::Error::Custom("Couldn't parse configuration".to_string())); + } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_config_matcher_parse() { + let m = "mail,news.info,warn;kern.=warn;syslog.warn;daemon.debug,info,warn"; + assert_eq!(format!("{:?}", super::parse_matcher(m)), + "Ok([(MultiFacility([NEWS, LOCAL0]), MultiLevel([WARNING, NONE])), \ + (Facility(KERN), ExactLevel(WARNING)), (Facility(SYSLOG), Level(WARNING)), \ + (Facility(DAEMON), MultiLevel([WARNING, NONE, NONE]))])"); + + let m = "mail, news.info; kern.warn"; + + assert_eq!(format!("{:?}", super::parse_matcher(m)), "Ok([(MultiFacility([NEWS, LOCAL0]), Level(INFO)), (Facility(KERN), Level(WARNING))])"); + } +}