subcomponent 0.1.0

A components orchestrator
/*
 * Copyright (c) 2016 Jean Guyomarc'h
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

//! Module providing the subcomponent's logging facility.

/*
 * This is strongly inspired by the TermLogger from simplelog.
 */

extern crate log;
extern crate std;
extern crate term;

use utils;
use self::SubLoggerError::SetLogger;
use std::error::Error;


/// SubLogger error type
#[derive(Debug)]
pub enum SubLoggerError {
    SetLogger(log::SetLoggerError),
}

impl std::fmt::Display for SubLoggerError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.description())
    }
}

impl From<log::SetLoggerError> for SubLoggerError {
    fn from(error: log::SetLoggerError) -> Self {
        SetLogger(error)
    }
}

impl std::error::Error for SubLoggerError {
    fn description(&self) -> &str {
        match *self {
            SetLogger(ref err) => err.description(),
        }
    }
}

/// Subcomponent logger - writes its messages in stderr, with fancy colors
/// and stuff using the term crate.
pub struct SubLogger {
    level: log::LogLevelFilter,
    stream: std::sync::Mutex<Box<term::StderrTerminal>>,
    tty: bool,
}

impl log::Log for SubLogger {
    fn enabled(&self, metadata: &log::LogMetadata) -> bool {
        metadata.level() <= self.level
    }

    fn log(&self, record: &log::LogRecord) {
        if self.enabled(record.metadata()) {
            let _ = self.log_handle(record);
        }
    }
}


impl SubLogger {
    fn log_handle(&self, record: &log::LogRecord) -> Result<(), std::io::Error> {
        let (color, prefix) = match record.level() {
            log::LogLevel::Error => ( term::color::RED,    "error: " ),
            log::LogLevel::Warn  => ( term::color::YELLOW, "warning: " ),
            log::LogLevel::Info  => ( term::color::GREEN,  "info: " ),
            log::LogLevel::Debug => ( term::color::BLUE,   "debug: " ),
            log::LogLevel::Trace => ( term::color::WHITE,  "trace: " )
        };
        let mut term = self.stream.lock().unwrap();

        /*
         * We don't want to print fancy stuff on non-tty outputs,
         * as it would be just disgusting.
         */
        if self.tty {
            try!(term.fg(color));
            try!(term.attr(term::Attr::Bold));
        }
        try!(write!(term, "{}", prefix));
        if self.tty {
            try!(term.reset());
        }
        try!(writeln!(term, "{}", record.args()));
        try!(term.flush());
        Ok(())
    }

    pub fn init(log_level: log::LogLevelFilter) -> Result<(), SubLoggerError> {
        /* Put an stderr term handler into a mutex */
        let term_std = term::stderr().unwrap();
        let stream = std::sync::Mutex::new(term_std);
        let tty = utils::isatty(&utils::Output::Stderr);

        /* Create the sublogger object */
        let logger = Box::new(SubLogger {
            level: log_level,
            stream: stream,
            tty: tty,
        });

        /* Use the sublogger */
        try!(log::set_logger(|max_log_level| {
            max_log_level.set(log_level);
            logger
        }));
        Ok(())
    }
}

pub fn init(level: log::LogLevelFilter) {
    if let Err(err) = SubLogger::init(level) {
        panic!("*** Failed to initialize logger: {}", err);
    }
}