use log::LogLevelFilter;
use std::any::Any;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::error;
use std::fmt;
use std::time::Duration;
use typemap::{Key, ShareMap};
use serde_value::Value;
use serde::Deserialize as SerdeDeserialize;
use append::file::FileAppenderDeserializer;
use append::console::ConsoleAppenderDeserializer;
use filter::Filter;
use filter::threshold::ThresholdFilterDeserializer;
use config;
use encode::pattern::PatternEncoderDeserializer;
use PrivateConfigErrorsExt;
pub mod raw;
struct KeyAdaptor<T: ?Sized>(PhantomData<T>);
impl<T: ?Sized + Any> Key for KeyAdaptor<T> {
type Value = HashMap<String, Box<Deserialize<Trait = T>>>;
}
pub trait Deserialize: Send + Sync + 'static {
type Trait: ?Sized;
fn deserialize(&self,
config: Value,
deserializers: &Deserializers)
-> Result<Box<Self::Trait>, Box<error::Error>>;
}
pub struct Deserializers(ShareMap);
impl Default for Deserializers {
fn default() -> Deserializers {
let mut deserializers = Deserializers::new();
deserializers.insert("file".to_owned(), Box::new(FileAppenderDeserializer));
deserializers.insert("console".to_owned(), Box::new(ConsoleAppenderDeserializer));
deserializers.insert("threshold".to_owned(),
Box::new(ThresholdFilterDeserializer));
deserializers.insert("pattern".to_owned(), Box::new(PatternEncoderDeserializer));
deserializers
}
}
impl Deserializers {
pub fn new() -> Deserializers {
Deserializers(ShareMap::custom())
}
pub fn insert<T: ?Sized + Any>(&mut self, kind: String, builder: Box<Deserialize<Trait = T>>) {
self.0.entry::<KeyAdaptor<T>>().or_insert(HashMap::new()).insert(kind, builder);
}
pub fn get<T: ?Sized + Any>(&self, kind: &str) -> Option<&Deserialize<Trait = T>> {
self.0.get::<KeyAdaptor<T>>().and_then(|m| m.get(kind)).map(|b| &**b)
}
pub fn deserialize<T: ?Sized + Any>(&self,
trait_: &str,
kind: &str,
config: Value)
-> Result<Box<T>, Box<error::Error>> {
match self.get(kind) {
Some(b) => b.deserialize(config, self),
None => Err(format!("no {} builder for kind `{}` registered", trait_, kind).into()),
}
}
}
#[derive(Debug)]
pub enum Error {
Deserialization(Box<error::Error>),
Config(config::Error),
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::Deserialization(ref err) => {
write!(fmt, "Error deserializing component: {}", 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 configuration file into a log4rs `Config`"
}
fn cause(&self) -> Option<&error::Error> {
match *self {
Error::Deserialization(ref err) => Some(&**err),
Error::Config(ref err) => Some(err),
}
}
}
#[derive(Copy, Clone)]
pub enum Format {
#[cfg(feature = "yaml")]
Yaml,
#[cfg(feature = "json")]
Json,
#[cfg(feature = "toml")]
Toml,
}
pub struct Config {
refresh_rate: Option<Duration>,
config: config::Config,
errors: Vec<Error>,
}
impl Config {
pub fn parse(config: &str,
format: Format,
deserializers: &Deserializers)
-> Result<Config, Box<error::Error>> {
let mut errors = vec![];
let config = try!(parse(format, config));
let raw::Config { refresh_rate,
root: raw_root,
appenders: raw_appenders,
loggers: raw_loggers,
.. } = config;
let root = match raw_root {
Some(raw_root) => {
config::Root::builder()
.appenders(raw_root.appenders)
.build(raw_root.level)
}
None => config::Root::builder().build(LogLevelFilter::Debug),
};
let mut config = config::Config::builder();
for (name, raw::Appender { kind, config: raw_config, filters }) in raw_appenders {
match deserializers.deserialize("appender", &kind, raw_config) {
Ok(appender_obj) => {
let mut builder = config::Appender::builder();
for raw::Filter { kind, config } in filters {
match deserializers.deserialize("filter", &kind, config) {
Ok(filter) => builder = builder.filter(filter),
Err(err) => errors.push(Error::Deserialization(err)),
}
}
config = config.appender(builder.build(name.clone(), appender_obj));
}
Err(err) => errors.push(Error::Deserialization(err)),
}
}
for (name, logger) in raw_loggers {
let raw::Logger { level, appenders, additive, .. } = logger;
let mut logger = config::Logger::builder().appenders(appenders);
if let Some(additive) = additive {
logger = logger.additive(additive);
}
config = config.logger(logger.build(name, level));
}
let (config, config_errors) = config.build_lossy(root);
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.map(|r| r),
config: config,
errors: errors,
};
Ok(config)
}
pub fn refresh_rate(&self) -> Option<Duration> {
self.refresh_rate
}
pub fn into_config(self) -> config::Config {
self.config
}
pub fn errors(&self) -> &[Error] {
&self.errors
}
}
fn parse(format: Format, _config: &str) -> Result<raw::Config, Box<error::Error>> {
match format {
#[cfg(feature = "yaml")]
Format::Yaml => ::serde_yaml::from_str(_config).map_err(Into::into),
#[cfg(feature = "json")]
Format::Json => ::serde_json::from_str(_config).map_err(Into::into),
#[cfg(feature = "toml")]
Format::Toml => {
let mut parser = ::toml::Parser::new(_config);
let table = match parser.parse() {
Some(table) => ::toml::Value::Table(table),
None => return Err(parser.errors.pop().unwrap().into()),
};
raw::Config::deserialize(&mut ::toml::Decoder::new(table)).map_err(Into::into)
}
}
}
#[cfg(test)]
#[allow(unused_imports)]
mod test {
use super::*;
#[test]
#[cfg(feature = "yaml")]
fn full_deserialize() {
let cfg = r#"
refresh_rate: 60
appenders:
console:
kind: console
filters:
- kind: threshold
level: debug
baz:
kind: file
path: /tmp/baz.log
encoder:
pattern: "%m"
root:
appenders:
- console
level: info
loggers:
foo::bar::baz:
level: warn
appenders:
- baz
additive: false
"#;
let config = Config::parse(cfg, Format::Yaml, &Deserializers::default()).unwrap();
assert!(config.errors().is_empty());
}
}