#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "http://www.rust-lang.org/favicon.ico",
html_root_url = "http://doc.rust-lang.org/log/")]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
extern crate regex;
use std::cell::RefCell;
use std::fmt;
use std::io::LineBufferedWriter;
use std::io;
use std::mem;
use std::os;
use std::rt;
use std::slice;
use std::sync::{Once, ONCE_INIT};
use regex::Regex;
use directive::LOG_LEVEL_NAMES;
#[macro_use]
pub mod macros;
mod directive;
pub const MAX_LOG_LEVEL: u32 = 255;
const DEFAULT_LOG_LEVEL: u32 = 1;
static mut LOG_LEVEL: u32 = MAX_LOG_LEVEL;
static mut DIRECTIVES: *const Vec<directive::LogDirective> =
0 as *const Vec<directive::LogDirective>;
static mut FILTER: *const Regex = 0 as *const _;
pub const DEBUG: u32 = 4;
pub const INFO: u32 = 3;
pub const WARN: u32 = 2;
pub const ERROR: u32 = 1;
thread_local!(static LOCAL_LOGGER: RefCell<Option<Box<Logger + Send>>> = {
RefCell::new(None)
});
pub trait Logger {
fn log(&mut self, record: &LogRecord);
}
struct DefaultLogger {
handle: LineBufferedWriter<io::stdio::StdWriter>,
}
#[derive(PartialEq, PartialOrd)]
pub struct LogLevel(pub u32);
impl Copy for LogLevel {}
impl fmt::Show for LogLevel {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let LogLevel(level) = *self;
match LOG_LEVEL_NAMES.get(level as uint - 1) {
Some(name) => name.fmt(fmt),
None => level.fmt(fmt)
}
}
}
impl Logger for DefaultLogger {
fn log(&mut self, record: &LogRecord) {
match writeln!(&mut self.handle,
"{}:{}: {}",
record.level,
record.module_path,
record.args) {
Err(e) => panic!("failed to log: {}", e),
Ok(()) => {}
}
}
}
impl Drop for DefaultLogger {
fn drop(&mut self) {
match self.handle.flush() {
Err(e) => panic!("failed to flush a logger: {}", e),
Ok(()) => {}
}
}
}
#[doc(hidden)]
pub fn log(level: u32, loc: &'static LogLocation, args: fmt::Arguments) {
match unsafe { FILTER.as_ref() } {
Some(filter) if !filter.is_match(args.to_string().as_slice()) => return,
_ => {}
}
let mut logger = LOCAL_LOGGER.with(|s| {
s.borrow_mut().take()
}).unwrap_or_else(|| {
box DefaultLogger { handle: io::stderr() } as Box<Logger + Send>
});
logger.log(&LogRecord {
level: LogLevel(level),
args: args,
file: loc.file,
module_path: loc.module_path,
line: loc.line,
});
set_logger(logger);
}
#[doc(hidden)]
#[inline(always)]
pub fn log_level() -> u32 { unsafe { LOG_LEVEL } }
pub fn set_logger(logger: Box<Logger + Send>) -> Option<Box<Logger + Send>> {
let mut l = Some(logger);
LOCAL_LOGGER.with(|slot| {
mem::replace(&mut *slot.borrow_mut(), l.take())
})
}
#[derive(Show)]
pub struct LogRecord<'a> {
pub module_path: &'a str,
pub level: LogLevel,
pub args: fmt::Arguments<'a>,
pub file: &'a str,
pub line: uint,
}
#[doc(hidden)]
pub struct LogLocation {
pub module_path: &'static str,
pub file: &'static str,
pub line: uint,
}
impl Copy for LogLocation {}
#[doc(hidden)]
pub fn mod_enabled(level: u32, module: &str) -> bool {
static INIT: Once = ONCE_INIT;
INIT.call_once(init);
if level > unsafe { LOG_LEVEL } { return false }
assert!(unsafe { !DIRECTIVES.is_null() });
enabled(level, module, unsafe { (*DIRECTIVES).iter() })
}
fn enabled(level: u32,
module: &str,
iter: slice::Iter<directive::LogDirective>)
-> bool {
for directive in iter.rev() {
match directive.name {
Some(ref name) if !module.starts_with(name.as_slice()) => {},
Some(..) | None => {
return level <= directive.level
}
}
}
level <= DEFAULT_LOG_LEVEL
}
fn init() {
let (mut directives, filter) = match os::getenv("RUST_LOG") {
Some(spec) => directive::parse_logging_spec(spec.as_slice()),
None => (Vec::new(), None),
};
directives.sort_by(|a, b| {
let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0);
let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0);
alen.cmp(&blen)
});
let max_level = {
let max = directives.iter().max_by(|d| d.level);
max.map(|d| d.level).unwrap_or(DEFAULT_LOG_LEVEL)
};
unsafe {
LOG_LEVEL = max_level;
assert!(FILTER.is_null());
match filter {
Some(f) => FILTER = mem::transmute(box f),
None => {}
}
assert!(DIRECTIVES.is_null());
DIRECTIVES = mem::transmute(box directives);
rt::at_exit(|| {
assert!(!DIRECTIVES.is_null());
let _directives: Box<Vec<directive::LogDirective>> =
mem::transmute(DIRECTIVES);
DIRECTIVES = 0 as *const Vec<directive::LogDirective>;
if !FILTER.is_null() {
let _filter: Box<Regex> = mem::transmute(FILTER);
FILTER = 0 as *const _;
}
});
}
}
#[cfg(test)]
mod tests {
use super::enabled;
use directive::LogDirective;
#[test]
fn match_full_path() {
let dirs = [
LogDirective {
name: Some("crate2".to_string()),
level: 3
},
LogDirective {
name: Some("crate1::mod1".to_string()),
level: 2
}
];
assert!(enabled(2, "crate1::mod1", dirs.iter()));
assert!(!enabled(3, "crate1::mod1", dirs.iter()));
assert!(enabled(3, "crate2", dirs.iter()));
assert!(!enabled(4, "crate2", dirs.iter()));
}
#[test]
fn no_match() {
let dirs = [
LogDirective { name: Some("crate2".to_string()), level: 3 },
LogDirective { name: Some("crate1::mod1".to_string()), level: 2 }
];
assert!(!enabled(2, "crate3", dirs.iter()));
}
#[test]
fn match_beginning() {
let dirs = [
LogDirective { name: Some("crate2".to_string()), level: 3 },
LogDirective { name: Some("crate1::mod1".to_string()), level: 2 }
];
assert!(enabled(3, "crate2::mod1", dirs.iter()));
}
#[test]
fn match_beginning_longest_match() {
let dirs = [
LogDirective { name: Some("crate2".to_string()), level: 3 },
LogDirective { name: Some("crate2::mod".to_string()), level: 4 },
LogDirective { name: Some("crate1::mod1".to_string()), level: 2 }
];
assert!(enabled(4, "crate2::mod1", dirs.iter()));
assert!(!enabled(4, "crate2", dirs.iter()));
}
#[test]
fn match_default() {
let dirs = [
LogDirective { name: None, level: 3 },
LogDirective { name: Some("crate1::mod1".to_string()), level: 2 }
];
assert!(enabled(2, "crate1::mod1", dirs.iter()));
assert!(enabled(3, "crate2::mod2", dirs.iter()));
}
#[test]
fn zero_level() {
let dirs = [
LogDirective { name: None, level: 3 },
LogDirective { name: Some("crate1::mod1".to_string()), level: 0 }
];
assert!(!enabled(1, "crate1::mod1", dirs.iter()));
assert!(enabled(3, "crate2::mod2", dirs.iter()));
}
}