#[macro_use]
extern crate lazy_static;
#[cfg(not(test))]
extern crate log;
extern crate thread_id;
#[cfg(test)]
#[macro_use]
extern crate log;
#[cfg(test)]
extern crate regex;
use log::{LevelFilter, Log, Metadata, Record};
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::Path;
use std::sync::Mutex;
use std::time::Instant;
lazy_static! {
static ref LOGGER: SimpleLogger = SimpleLogger {
inner: Mutex::new(None),
};
}
struct SimpleLogger {
inner: Mutex<Option<SimpleLoggerInner>>,
}
impl SimpleLogger {
fn renew<T: Write + Send + 'static>(&self, sink: T) {
*self.inner.lock().unwrap() = Some(SimpleLoggerInner {
start: Instant::now(),
sink: Box::new(sink),
});
}
}
impl Log for SimpleLogger {
fn enabled(&self, _: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
if let Some(ref mut inner) = *self.inner.lock().unwrap() {
inner.log(record);
}
}
fn flush(&self) {}
}
struct SimpleLoggerInner {
start: Instant,
sink: Box<Write + Send>,
}
impl SimpleLoggerInner {
fn log(&mut self, record: &Record) {
let now = self.start.elapsed();
let seconds = now.as_secs();
let hours = seconds / 3600;
let minutes = (seconds / 60) % 60;
let seconds = seconds % 60;
let miliseconds = now.subsec_nanos() / 1_000_000;
let _ = write!(
self.sink,
"[{:02}:{:02}:{:02}.{:03}] ({:x}) {:6} {}\n",
hours,
minutes,
seconds,
miliseconds,
thread_id::get(),
record.level(),
record.args()
);
}
}
pub fn log_to_file<T: AsRef<Path>>(
path: T,
max_log_level: LevelFilter,
) -> io::Result<()> {
let file = File::create(path)?;
log_to(file, max_log_level);
Ok(())
}
pub fn log_to_stderr(max_log_level: LevelFilter) {
log_to(io::stderr(), max_log_level);
}
pub fn log_to<T: Write + Send + 'static>(sink: T, max_log_level: LevelFilter) {
LOGGER.renew(sink);
log::set_max_level(max_log_level);
let _ = log::set_logger(&*LOGGER);
assert_eq!(log::logger() as *const Log, &*LOGGER as *const Log);
}
#[cfg(test)]
mod tests {
use log_to;
use log::LevelFilter::Info;
use regex::Regex;
use std::io;
use std::io::Write;
use std::str;
use std::sync::{Arc, Mutex};
struct VecProxy(Arc<Mutex<Vec<u8>>>);
impl Write for VecProxy {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn test() {
let buf = Arc::new(Mutex::new(Vec::new()));
let proxy = VecProxy(buf.clone());
log_to(proxy, Info);
debug!("filtered");
assert!(buf.lock().unwrap().is_empty());
let pat = Regex::new(
r"^\[\d\d:\d\d:\d\d.\d\d\d] \([0-9a-zA-Z]+\) INFO test\n$",
)
.unwrap();
info!("test");
let line = str::from_utf8(&buf.lock().unwrap()).unwrap().to_owned();
assert!(pat.is_match(&line));
}
}