fastlog 0.2.0

A high performance asynchronous logger
Documentation
extern crate crossbeam_channel as channel;
extern crate log;
extern crate time;

use log::{Log, LevelFilter, Metadata, Record, SetLoggerError};
use std::fs::OpenOptions;
use std::io::Error as IoError;
use std::io::Write;
use std::path::PathBuf;
use time::{get_time, Timespec};


#[derive(Clone, Debug)]
enum LoggerInput {
    LogMsg(String),
    Flush,
    Quit,
}

#[derive(Clone, Debug)]
enum LoggerOutput {
    Flushed,
}

pub struct Logger {
    format: Box<Fn(Timespec, &Record) -> String + Sync + Send>,
    level: LevelFilter,
    queue: channel::Sender<LoggerInput>,
    notification: channel::Receiver<LoggerOutput>,
    worker_thread: Option<std::thread::JoinHandle<()>>,
}

unsafe impl Send for Logger {}
unsafe impl Sync for Logger {}

impl Logger {
    pub fn init(self) -> Result<(), SetLoggerError> {
        log::set_max_level(self.level);
        let boxed = Box::new(self);
        log::set_boxed_logger(boxed)
    }
}

impl Log for Logger {
    #[inline]
    fn enabled(&self, metadata: &Metadata) -> bool {
        self.level >= metadata.level()
    }

    fn log(&self, record: &Record) {
        let log_msg = (self.format)(get_time(), record);
        self.queue
            .send(LoggerInput::LogMsg(log_msg))
            .expect("logger queue closed when logging, this is a bug")
    }

    fn flush(&self) {
        self.queue
            .send(LoggerInput::Flush)
            .expect("logger queue closed when flushing, this is a bug");
        self.notification.recv()
            .expect("logger notification closed, this is a bug");
    }
}

impl Drop for Logger {
    fn drop(&mut self) {
        self.queue
            .send(LoggerInput::Quit)
            .expect("logger queue closed before joining logger thread, this is a bug");
        let join_handle = self.worker_thread
            .take()
            .expect("logger thread empty when dropping logger, this is a bug");
        join_handle.join()
            .expect("failed to join logger thread when dropping logger, this is a bug");
    }
}

pub struct LogBuilder {
    format: Box<Fn(Timespec, &Record) -> String + Sync + Send>,
    capacity: usize,
    level: LevelFilter,
    path: PathBuf,
    header: Vec<String>,
}

impl LogBuilder {
    #[inline]
    pub fn new() -> LogBuilder {
        LogBuilder {
            format: Box::new(|ts: Timespec, record: &Record| {
                format!("{:?} {}:{}: {}",
                        ts,
                        record.level(),
                        record.module_path().unwrap_or(""),
                        record.args())
            }),
            capacity: 2048,
            level: LevelFilter::Info,
            path: PathBuf::from("./current.log"),
            header: Vec::new(),
        }
    }

    #[inline]
    pub fn format<F: 'static>(&mut self, format: F) -> &mut LogBuilder
        where F: Fn(Timespec, &Record) -> String + Sync + Send
    {
        self.format = Box::new(format);
        self
    }

    #[inline]
    pub fn capacity(&mut self, capacity: usize) -> &mut LogBuilder {
        self.capacity = capacity;
        self
    }

    #[inline]
    pub fn file(&mut self, path: PathBuf) -> &mut LogBuilder {
        self.path = path;
        self
    }

    #[inline]
    pub fn max_log_level(&mut self, level: LevelFilter) -> &mut LogBuilder {
        self.level = level;
        self
    }

    #[inline]
    pub fn header(&mut self, header: Vec<String>) -> &mut LogBuilder {
        self.header = header;
        self
    }

    pub fn build(self) -> Result<Logger, IoError> {
        let (sync_sender, receiver) = channel::bounded(self.capacity);
        let (notification_sender, notification_receiver) = channel::bounded(1);
        let mut writer = try!(OpenOptions::new()
            .create(true)
            .append(true)
            .open(self.path));
        for line in &self.header {
            try!(writeln!(&mut writer, "{}", line));
        }
        let worker_thread = try!(std::thread::Builder::new()
            .name("logger".to_string())
            .spawn(move || loop {
                match receiver.recv() {
                    Ok(LoggerInput::LogMsg(msg)) => {
                        writeln!(&mut writer, "{}", msg).expect("logger write message failed");
                    }
                    Ok(LoggerInput::Flush) => {
                        notification_sender.send(LoggerOutput::Flushed).expect("logger notification failed");
                    }
                    Ok(LoggerInput::Quit) => {
                        break;
                    }
                    Err(e) => {
                        panic!("sender closed without sending a Quit first, this is a bug, {}", e);
                    }
                }
            }));
        Ok(Logger {
            format: self.format,
            level: self.level,
            queue: sync_sender,
            notification: notification_receiver,
            worker_thread: Some(worker_thread),
        })
    }
}