pub mod write;
use chrono;
use error::*;
use log::{self, LevelFilter, Log, Metadata, Record};
use logging::write::*;
use regex::Regex;
use std::collections::HashMap;
use std::io::Write;
use std::str::FromStr;
const RUST_LOG_ENV_VAR: &'static str = "RUST_LOG";
pub fn parse_log_level_filter(s: &str) -> Result<LevelFilter> {
lazy_static! {
static ref STRING_MAPPING: HashMap<String, LevelFilter> = {
let mut m = HashMap::new();
m.insert(
LevelFilter::Off.to_string().to_lowercase(),
LevelFilter::Off,
);
m.insert(
LevelFilter::Error.to_string().to_lowercase(),
LevelFilter::Error,
);
m.insert(
LevelFilter::Warn.to_string().to_lowercase(),
LevelFilter::Warn,
);
m.insert(
LevelFilter::Info.to_string().to_lowercase(),
LevelFilter::Info,
);
m.insert(
LevelFilter::Debug.to_string().to_lowercase(),
LevelFilter::Debug,
);
m.insert(
LevelFilter::Trace.to_string().to_lowercase(),
LevelFilter::Trace,
);
m
};
}
let normalized = s.trim().to_lowercase();
match STRING_MAPPING.get(&normalized) {
None => {
return Err(Error::InvalidArgument(format_err!(
"Invalid LevelFilter '{}'",
s
)))
}
Some(f) => Ok(*f),
}
}
pub struct LogFilter {
pub module: Option<Regex>,
pub level: LevelFilter,
}
impl LogFilter {
pub fn max_level_for(&self, module_path: &str) -> Option<LevelFilter> {
match self.module {
None => Some(self.level),
Some(ref module) => match module.is_match(module_path) {
false => None,
true => Some(self.level),
},
}
}
}
impl FromStr for LogFilter {
type Err = Error;
fn from_str(s: &str) -> Result<LogFilter> {
match s.rfind('=') {
None => Ok(LogFilter {
module: None,
level: parse_log_level_filter(s)?,
}),
Some(eq_pos) => {
let mut re: String = "^".to_owned();
re.push_str(&s[..eq_pos]);
Ok(LogFilter {
module: Some(Regex::new(&re)?),
level: parse_log_level_filter(&s[eq_pos + 1..])?,
})
}
}
}
}
pub struct LogFilters(pub Vec<LogFilter>);
impl LogFilters {
pub fn max_level_for(&self, module_path: &str) -> LevelFilter {
self.0
.iter()
.filter_map(|f| f.max_level_for(module_path))
.min()
.unwrap_or(LevelFilter::Trace)
}
}
impl FromStr for LogFilters {
type Err = Error;
fn from_str(s: &str) -> Result<LogFilters> {
let filters: Result<Vec<LogFilter>> = s.split(';').map(|f| f.parse()).collect();
Ok(LogFilters(filters?))
}
}
pub struct Options {
pub filters: LogFilters,
pub max_level: LevelFilter,
pub output_factory: LogOutputFactory,
pub panic_on_output_failure: bool,
pub always_flush: bool,
}
pub struct OptionsBuilder {
filters: Option<LogFilters>,
output_factory: Option<LogOutputFactory>,
panic_on_output_failure: Option<bool>,
always_flush: Option<bool>,
}
impl OptionsBuilder {
pub fn new() -> Self {
OptionsBuilder {
filters: None,
output_factory: None,
panic_on_output_failure: None,
always_flush: None,
}
}
pub fn set_filters(mut self, filters: LogFilters) -> Self {
self.filters = Some(filters);
self
}
pub fn set_output_factory(mut self, output_factory: LogOutputFactory) -> Self {
self.output_factory = Some(output_factory);
self
}
pub fn set_output_to<T: Write + Send + 'static>(self, output_writer: T) -> Self {
self.set_output_factory(new_log_output_factory(output_writer))
}
pub fn set_panic_on_output_failure(mut self, panic_on_output_failure: bool) -> Self {
self.panic_on_output_failure = Some(panic_on_output_failure);
self
}
pub fn set_always_flush(mut self, always_flush: bool) -> Self {
self.always_flush = Some(always_flush);
self
}
pub fn build(self) -> Result<Options> {
let filters: LogFilters = match self.filters {
None => match get_env_var(RUST_LOG_ENV_VAR)? {
None => LogFilters(vec![]),
Some(filters_str) => filters_str.parse()?,
},
Some(filters) => filters,
};
let max_level: LevelFilter = filters
.0
.iter()
.map(|f| f.level)
.max()
.unwrap_or(LevelFilter::Trace);
Ok(Options {
filters: filters,
max_level: max_level,
output_factory: self.output_factory
.unwrap_or_else(|| Box::new(|| Box::new(::std::io::stderr()))),
panic_on_output_failure: self.panic_on_output_failure.unwrap_or(false),
always_flush: self.always_flush.unwrap_or(false),
})
}
}
fn get_env_var(key: &str) -> Result<Option<String>> {
match ::std::env::var(key) {
Ok(v) => Ok(Some(v)),
Err(e) => match e {
::std::env::VarError::NotPresent => Ok(None),
_ => Err(Error::EnvVar(e)),
},
}
}
pub fn format_log_record(record: &Record) -> String {
format!(
"[{} {}:{}] {} - {}",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
record.file().unwrap_or("UNKNOWN_FILE"),
record
.line()
.map_or("UNKNOWN_LINE".to_owned(), |l| l.to_string()),
record.level(),
record.args()
)
}
pub struct Logger {
options: Options,
}
impl Logger {
pub fn new(options: Options) -> Self {
Logger { options: options }
}
}
impl Log for Logger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.options.max_level
}
fn log(&self, record: &Record) {
if record.level()
> self.options
.filters
.max_level_for(record.module_path().unwrap_or(""))
{
return;
}
let res = write!(
(self.options.output_factory)(),
"{}\n",
format_log_record(record)
);
if self.options.panic_on_output_failure {
if let Err(e) = res {
panic!("Failed to write log output: {}", e);
} else {
return;
}
}
if self.options.always_flush {
self.flush();
}
}
fn flush(&self) {
let res = (self.options.output_factory)().flush();
if self.options.panic_on_output_failure {
if let Err(e) = res {
panic!("Failed to flush log output: {}", e);
}
}
}
}
pub fn try_init(options: Options) -> Result<()> {
let logger = Logger::new(options);
log::set_max_level(logger.options.max_level);
log::set_boxed_logger(Box::new(logger))?;
Ok(())
}
pub fn init(options: Options) {
try_init(options).unwrap();
}