Mercurial > lbo > hg > syslog
view src/config.rs @ 17:4caa98d804bd draft
Implement filter rules
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Sat, 03 Dec 2016 14:17:18 +0100 |
parents | 2d2296e5bcf4 |
children | 4914bd1e162f |
line wrap: on
line source
//! 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, } /// 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, // 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 { fn default() -> CompressType { CompressType::NoCompression } } #[derive(Clone,Default)] pub struct File { name: String, location: String, /// bytes max_size: usize, /// seconds max_age: u64, history: i32, compress: CompressType, } #[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) } 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 { 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))])"); } }