#![doc(html_root_url = "https://docs.rs/log4rs/0.8.0")]
#![warn(missing_docs)]
#[cfg(feature = "antidote")]
extern crate antidote;
#[cfg(feature = "chrono")]
extern crate chrono;
extern crate crossbeam;
#[cfg(feature = "flate2")]
extern crate flate2;
extern crate fnv;
#[cfg(feature = "humantime")]
extern crate humantime;
#[cfg(all(not(windows), feature = "libc"))]
extern crate libc;
extern crate log;
#[cfg(feature = "log-mdc")]
extern crate log_mdc;
#[cfg(feature = "serde")]
extern crate serde;
#[cfg(feature = "serde_json")]
extern crate serde_json;
#[cfg(feature = "serde-value")]
extern crate serde_value;
#[cfg(feature = "xml_format")]
extern crate serde_xml_rs;
#[cfg(feature = "serde_yaml")]
extern crate serde_yaml;
#[cfg(feature = "toml")]
extern crate toml;
#[cfg(feature = "typemap")]
extern crate typemap;
#[cfg(all(windows, feature = "winapi"))]
extern crate winapi;
#[cfg(feature = "serde_derive")]
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
extern crate tempdir;
use crossbeam::sync::ArcCell;
use fnv::FnvHasher;
use std::cmp;
use std::collections::HashMap;
use std::error;
use std::hash::BuildHasherDefault;
use std::io;
use std::io::prelude::*;
use std::sync::Arc;
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
#[cfg(feature = "file")]
pub use priv_file::{init_file, Error};
use append::Append;
use config::Config;
use filter::Filter;
pub mod append;
pub mod config;
pub mod filter;
#[cfg(feature = "file")]
pub mod file;
pub mod encode;
#[cfg(feature = "file")]
mod priv_file;
#[cfg(feature = "console_writer")]
mod priv_io;
type FnvHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
struct ConfiguredLogger {
level: LevelFilter,
appenders: Vec<usize>,
children: FnvHashMap<String, ConfiguredLogger>,
}
impl ConfiguredLogger {
fn add(&mut self, path: &str, mut appenders: Vec<usize>, additive: bool, level: LevelFilter) {
let (part, rest) = match path.find("::") {
Some(idx) => (&path[..idx], &path[idx + 2..]),
None => (path, ""),
};
if let Some(child) = self.children.get_mut(part) {
child.add(rest, appenders, additive, level);
return;
}
let child = if rest.is_empty() {
if additive {
appenders.extend(self.appenders.iter().cloned());
}
ConfiguredLogger {
level: level,
appenders: appenders,
children: FnvHashMap::default(),
}
} else {
let mut child = ConfiguredLogger {
level: self.level,
appenders: self.appenders.clone(),
children: FnvHashMap::default(),
};
child.add(rest, appenders, additive, level);
child
};
self.children.insert(part.to_owned(), child);
}
fn max_log_level(&self) -> LevelFilter {
let mut max = self.level;
for child in self.children.values() {
max = cmp::max(max, child.max_log_level());
}
max
}
fn find(&self, path: &str) -> &ConfiguredLogger {
let mut node = self;
for part in path.split("::") {
match node.children.get(part) {
Some(child) => node = child,
None => break,
}
}
node
}
fn enabled(&self, level: Level) -> bool {
self.level >= level
}
fn log(&self, record: &log::Record, appenders: &[Appender]) {
if self.enabled(record.level()) {
for &idx in &self.appenders {
if let Err(err) = appenders[idx].append(record) {
handle_error(&*err);
}
}
}
}
}
struct Appender {
appender: Box<Append>,
filters: Vec<Box<Filter>>,
}
impl Appender {
fn append(&self, record: &Record) -> Result<(), Box<error::Error + Sync + Send>> {
for filter in &self.filters {
match filter.filter(record) {
filter::Response::Accept => break,
filter::Response::Neutral => {}
filter::Response::Reject => return Ok(()),
}
}
self.appender.append(record)
}
fn flush(&self) {
self.appender.flush();
}
}
struct SharedLogger {
root: ConfiguredLogger,
appenders: Vec<Appender>,
}
impl SharedLogger {
fn new(config: config::Config) -> SharedLogger {
let (appenders, root, mut loggers) = config.unpack();
let root = {
let appender_map = appenders
.iter()
.enumerate()
.map(|(i, appender)| (appender.name(), i))
.collect::<HashMap<_, _>>();
let mut root = ConfiguredLogger {
level: root.level(),
appenders: root.appenders()
.iter()
.map(|appender| appender_map[&**appender])
.collect(),
children: FnvHashMap::default(),
};
loggers.sort_by_key(|l| l.name().len());
for logger in loggers {
let appenders = logger
.appenders()
.iter()
.map(|appender| appender_map[&**appender])
.collect();
root.add(logger.name(), appenders, logger.additive(), logger.level());
}
root
};
let appenders = appenders
.into_iter()
.map(|appender| {
let (_, appender, filters) = appender.unpack();
Appender {
appender: appender,
filters: filters,
}
})
.collect();
SharedLogger {
root: root,
appenders: appenders,
}
}
}
struct Logger(Arc<ArcCell<SharedLogger>>);
impl Logger {
fn new(config: config::Config) -> Logger {
Logger(Arc::new(ArcCell::new(Arc::new(SharedLogger::new(config)))))
}
fn max_log_level(&self) -> LevelFilter {
self.0.get().root.max_log_level()
}
}
impl log::Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
self.0
.get()
.root
.find(metadata.target())
.enabled(metadata.level())
}
fn log(&self, record: &log::Record) {
let shared = self.0.get();
shared
.root
.find(record.target())
.log(record, &shared.appenders);
}
fn flush(&self) {
for appender in &self.0.get().appenders {
appender.flush();
}
}
}
fn handle_error<E: error::Error + ?Sized>(e: &E) {
let _ = writeln!(io::stderr(), "log4rs: {}", e);
}
pub fn init_config(config: config::Config) -> Result<Handle, SetLoggerError> {
let logger = Logger::new(config);
log::set_max_level(logger.max_log_level());
let handle = Handle {
shared: logger.0.clone(),
};
log::set_boxed_logger(Box::new(logger)).map(|()| handle)
}
pub struct Handle {
shared: Arc<ArcCell<SharedLogger>>,
}
impl Handle {
pub fn set_config(&self, config: Config) {
let shared = SharedLogger::new(config);
log::set_max_level(shared.root.max_log_level());
self.shared.set(Arc::new(shared));
}
}
trait ErrorInternals {
fn new(message: String) -> Self;
}
trait ConfigPrivateExt {
fn unpack(self) -> (Vec<config::Appender>, config::Root, Vec<config::Logger>);
}
trait PrivateConfigAppenderExt {
fn unpack(self) -> (String, Box<Append>, Vec<Box<Filter>>);
}
#[cfg(test)]
mod test {
use log::{Level, LevelFilter, Log};
use super::*;
#[test]
fn enabled() {
let root = config::Root::builder().build(LevelFilter::Debug);
let mut config = config::Config::builder();
let logger = config::Logger::builder().build("foo::bar", LevelFilter::Trace);
config = config.logger(logger);
let logger = config::Logger::builder().build("foo::bar::baz", LevelFilter::Off);
config = config.logger(logger);
let logger = config::Logger::builder().build("foo::baz::buz", LevelFilter::Error);
config = config.logger(logger);
let config = config.build(root).unwrap();
let logger = super::Logger::new(config);
assert!(logger.enabled(&Metadata::builder().level(Level::Warn).target("bar").build()));
assert!(!logger.enabled(&Metadata::builder()
.level(Level::Trace)
.target("bar")
.build()));
assert!(
logger.enabled(&Metadata::builder()
.level(Level::Debug)
.target("foo")
.build())
);
assert!(
logger.enabled(&Metadata::builder()
.level(Level::Trace)
.target("foo::bar")
.build())
);
assert!(!logger.enabled(&Metadata::builder()
.level(Level::Error)
.target("foo::bar::baz")
.build()));
assert!(
logger.enabled(&Metadata::builder()
.level(Level::Debug)
.target("foo::bar::bazbuz")
.build())
);
assert!(!logger.enabled(&Metadata::builder()
.level(Level::Error)
.target("foo::bar::baz::buz")
.build()));
assert!(!logger.enabled(&Metadata::builder()
.level(Level::Warn)
.target("foo::baz::buz")
.build()));
assert!(!logger.enabled(&Metadata::builder()
.level(Level::Warn)
.target("foo::baz::buz::bar")
.build()));
assert!(
logger.enabled(&Metadata::builder()
.level(Level::Error)
.target("foo::baz::buz::bar")
.build())
);
}
}