use log::LogLevelFilter;
use std::collections::HashMap;
use std::default::Default;
use std::error;
use std::fmt;
use std::time::Duration;
use toml_parser::{self, Value};
use appender::{FileAppender, ConsoleAppender};
use filter::{ThresholdFilter};
use config;
use pattern::PatternLayout;
use {Append, Filter, PrivateTomlConfigExt, PrivateConfigErrorsExt};
mod raw;
pub trait CreateAppender: Send + 'static {
fn create_appender(&self, config: &toml_parser::Table)
-> Result<Box<Append>, Box<error::Error>>;
}
pub trait CreateFilter: Send + 'static {
fn create_filter(&self, config: &toml_parser::Table)
-> Result<Box<Filter>, Box<error::Error>>;
}
pub struct Creator {
appenders: HashMap<String, Box<CreateAppender>>,
filters: HashMap<String, Box<CreateFilter>>,
}
impl Default for Creator {
fn default() -> Creator {
let mut creator = Creator::new();
creator.add_appender("file", Box::new(FileAppenderCreator));
creator.add_appender("console", Box::new(ConsoleAppenderCreator));
creator.add_filter("threshold", Box::new(ThresholdFilterCreator));
creator
}
}
impl Creator {
pub fn new() -> Creator {
Creator {
appenders: HashMap::new(),
filters: HashMap::new(),
}
}
pub fn add_appender(&mut self, kind: &str, creator: Box<CreateAppender>) {
self.appenders.insert(kind.to_string(), creator);
}
pub fn add_filter(&mut self, kind: &str, creator: Box<CreateFilter>) {
self.filters.insert(kind.to_string(), creator);
}
fn create_appender(&self, kind: &str, config: &toml_parser::Table)
-> Result<Box<Append>, Box<error::Error>> {
match self.appenders.get(kind) {
Some(creator) => creator.create_appender(config),
None => Err(Box::new(StringError(format!("No creator registered for appender kind \"{}\"", kind))))
}
}
fn create_filter(&self, kind: &str, config: &toml_parser::Table)
-> Result<Box<Filter>, Box<error::Error>> {
match self.filters.get(kind) {
Some(creator) => creator.create_filter(config),
None => Err(Box::new(StringError(format!("No creator registered for filter kind \"{}\"", kind))))
}
}
}
#[derive(Debug)]
pub struct ParseErrors {
errors: Vec<String>,
}
impl fmt::Display for ParseErrors {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
for error in &self.errors {
try!(writeln!(fmt, "{}", error));
}
Ok(())
}
}
impl error::Error for ParseErrors {
fn description(&self) -> &str {
"Errors encountered when parsing a log4rs TOML config"
}
}
#[derive(Debug)]
pub enum Error {
AppenderCreation(String, Box<error::Error>),
FilterCreation(String, Box<error::Error>),
Config(config::Error),
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::AppenderCreation(ref appender, ref err) => {
write!(fmt, "Error creating appender `{}`: {}", appender, err)
}
Error::FilterCreation(ref appender, ref err) => {
write!(fmt, "Error creating filter for appender `{}`: {}", appender, err)
}
Error::Config(ref err) => write!(fmt, "Error creating config: {}", err),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
"An error encountered when deserializing a TOML configuration into a log4rs `Config`"
}
fn cause(&self) -> Option<&error::Error> {
match *self {
Error::AppenderCreation(_, ref err) => Some(&**err),
Error::FilterCreation(_, ref err) => Some(&**err),
Error::Config(ref err) => Some(err),
}
}
}
#[derive(Debug)]
pub struct Errors {
errors: Vec<Error>,
}
impl Errors {
pub fn errors(&self) -> &[Error] {
&self.errors
}
}
impl fmt::Display for Errors {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
for error in &self.errors {
try!(writeln!(fmt, "{}", error));
}
Ok(())
}
}
impl error::Error for Errors {
fn description(&self) -> &str {
"Errors encountered when deserializing a TOML configuration into a log4rs `Config`"
}
}
pub struct Config {
refresh_rate: Option<Duration>,
config: config::Config,
}
impl Config {
pub fn parse(config: &str, creator: &Creator)
-> Result<(Config, Result<(), Errors>), ParseErrors> {
let mut errors = vec![];
let config = match raw::parse(config) {
Ok(config) => config,
Err(errors) => return Err(ParseErrors { errors: errors }),
};
let raw::Config {
refresh_rate,
root: raw_root,
appenders: raw_appenders,
loggers: raw_loggers,
} = config;
let root = match raw_root {
Some(raw_root) => {
let mut root = config::Root::builder(raw_root.level);
if let Some(appenders) = raw_root.appenders {
root = root.appenders(appenders);
}
root.build()
}
None => config::Root::builder(LogLevelFilter::Debug).build(),
};
let mut config = config::Config::builder(root);
for (name, appender) in raw_appenders {
match creator.create_appender(&appender.kind, &appender.config) {
Ok(appender_obj) => {
let mut builder = config::Appender::builder(name.clone(), appender_obj);
for filter in appender.filters.unwrap_or(vec![]) {
match creator.create_filter(&filter.kind, &filter.config) {
Ok(filter) => builder= builder.filter(filter),
Err(err) => errors.push(Error::FilterCreation(name.clone(), err)),
}
}
config = config.appender(builder.build());
}
Err(err) => errors.push(Error::AppenderCreation(name, err)),
}
}
for logger in raw_loggers {
let raw::Logger { name, level, appenders, additive } = logger;
let mut logger = config::Logger::builder(name, level);
if let Some(appenders) = appenders {
logger = logger.appenders(appenders);
}
if let Some(additive) = additive {
logger = logger.additive(additive);
}
config = config.logger(logger.build());
}
let (config, config_errors) = config.build_lossy();
if let Err(config_errors) = config_errors {
for error in config_errors.unpack() {
errors.push(Error::Config(error));
}
}
let config = Config {
refresh_rate: refresh_rate,
config: config
};
let errors = if errors.is_empty() {
Ok(())
} else {
Err(Errors {
errors: errors
})
};
Ok((config, errors))
}
pub fn refresh_rate(&self) -> Option<Duration> {
self.refresh_rate
}
pub fn config(&self) -> &config::Config {
&self.config
}
}
impl PrivateTomlConfigExt for Config {
fn unpack(self) -> (Option<Duration>, config::Config) {
let Config { refresh_rate, config } = self;
(refresh_rate, config)
}
}
#[derive(Debug)]
struct StringError(String);
impl fmt::Display for StringError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(&self.0)
}
}
impl error::Error for StringError {
fn description(&self) -> &str {
&self.0
}
}
impl error::FromError<String> for StringError {
fn from_error(s: String) -> StringError {
StringError(s)
}
}
pub struct FileAppenderCreator;
impl CreateAppender for FileAppenderCreator {
fn create_appender(&self, config: &toml_parser::Table)
-> Result<Box<Append>, Box<error::Error>> {
let path = match config.get("path") {
Some(&Value::String(ref path)) => path,
Some(_) => return Err(Box::new(StringError("`path` must be a string".to_string()))),
None => return Err(Box::new(StringError("`path` is required".to_string()))),
};
let mut appender = FileAppender::builder(path);
match config.get("pattern") {
Some(&Value::String(ref pattern)) => {
appender = appender.pattern(try!(PatternLayout::new(pattern)));
}
Some(_) => return Err(Box::new(StringError("`pattern` must be a string".to_string()))),
None => {}
}
match config.get("append") {
Some(&Value::Boolean(append)) => appender = appender.append(append),
None => {}
Some(_) => return Err(Box::new(StringError("`append` must be a bool".to_string()))),
}
match appender.build() {
Ok(appender) => Ok(Box::new(appender)),
Err(err) => Err(Box::new(err))
}
}
}
pub struct ConsoleAppenderCreator;
impl CreateAppender for ConsoleAppenderCreator {
fn create_appender(&self, config: &toml_parser::Table)
-> Result<Box<Append>, Box<error::Error>> {
let mut appender = ConsoleAppender::builder();
match config.get("pattern") {
Some(&Value::String(ref pattern)) => {
appender = appender.pattern(try!(PatternLayout::new(pattern)));
}
Some(_) => return Err(Box::new(StringError("`pattern` must be a string".to_string()))),
None => {}
}
Ok(Box::new(appender.build()))
}
}
pub struct ThresholdFilterCreator;
impl CreateFilter for ThresholdFilterCreator {
fn create_filter(&self, config: &toml_parser::Table)
-> Result<Box<Filter>, Box<error::Error>> {
let level = match config.get("level") {
Some(&Value::String(ref level)) => level,
Some(_) => return Err(Box::new(StringError("`level` must be a string".to_string()))),
None => return Err(Box::new(StringError("`level` must be provided".to_string()))),
};
let level = match level.parse() {
Ok(level) => level,
Err(_) => return Err(Box::new(StringError(format!("Invalid `level` \"{}\"", level)))),
};
Ok(Box::new(ThresholdFilter::new(level)))
}
}