extern crate log;
extern crate atty;
extern crate ansi_term;
use log::{SetLoggerError};
use std::io::{self, Write};
use ansi_term::Colour;
pub const DEFAULT_COLORS: bool = true;
pub const DEFAULT_DEBUG_COLOR: Colour = Colour::Fixed(7); pub const DEFAULT_ERROR_COLOR: Colour = Colour::Fixed(9); pub const DEFAULT_INCLUDE_LEVEL: bool = false;
pub const DEFAULT_INCLUDE_LINE_NUMBERS: bool = false;
pub const DEFAULT_INCLUDE_MODULE_PATH: bool = true;
pub const DEFAULT_INFO_COLOR: Colour = Colour::Fixed(10); pub const DEFAULT_LEVEL: log::Level = log::Level::Warn;
pub const DEFAULT_OFFSET: u64 = 1;
pub const DEFAULT_SEPARATOR: &str = ": ";
pub const DEFAULT_TRACE_COLOR: Colour = Colour::Fixed(8); pub const DEFAULT_WARN_COLOR: Colour = Colour::Fixed(11);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Output {
Stderr,
Stdout,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct Level {
output: Output,
color: Colour,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Logger {
colors: bool,
include_level: bool,
include_line_numbers: bool,
include_module_path: bool,
level: log::Level,
offset: u64,
separator: String,
verbosity: Option<u64>,
error: Level,
warn: Level,
info: Level,
debug: Level,
trace: Level,
}
impl Logger {
pub fn new() -> Logger {
Logger {
colors: DEFAULT_COLORS && atty::is(atty::Stream::Stdout) && atty::is(atty::Stream::Stderr),
include_level: DEFAULT_INCLUDE_LEVEL,
include_line_numbers: DEFAULT_INCLUDE_LINE_NUMBERS,
include_module_path: DEFAULT_INCLUDE_MODULE_PATH,
level: DEFAULT_LEVEL,
offset: DEFAULT_OFFSET,
separator: String::from(DEFAULT_SEPARATOR),
verbosity: None,
error: Level {
output: Output::Stderr,
color: DEFAULT_ERROR_COLOR,
},
warn: Level {
output: Output::Stderr,
color: DEFAULT_WARN_COLOR,
},
info: Level {
output: Output::Stdout,
color: DEFAULT_INFO_COLOR,
},
debug: Level {
output: Output::Stdout,
color: DEFAULT_DEBUG_COLOR,
},
trace: Level {
output: Output::Stdout,
color: DEFAULT_TRACE_COLOR,
}
}
}
pub fn color(mut self, l: &log::Level, c: Colour) -> Self {
match *l {
log::Level::Error => self.error.color = c,
log::Level::Warn => self.warn.color = c,
log::Level::Info => self.info.color = c,
log::Level::Debug => self.debug.color = c,
log::Level::Trace => self.trace.color = c,
}
self
}
pub fn separator(mut self, s: &str) -> Self {
self.separator = String::from(s);
self
}
pub fn colors(mut self, c: bool) -> Self {
self.colors = c && atty::is(atty::Stream::Stdout) && atty::is(atty::Stream::Stderr);
self
}
pub fn no_colors(mut self) -> Self {
self. colors = false;
self
}
pub fn line_numbers(mut self, i: bool) -> Self {
self.include_line_numbers = i;
self
}
pub fn level(mut self, i: bool) -> Self {
self.include_level = i;
self
}
pub fn max_level(mut self, l: log::Level) -> Self {
self.level = l;
self.verbosity = None;
self
}
pub fn module_path(mut self, i: bool) -> Self {
self.include_module_path = i;
self
}
pub fn no_module_path(mut self) -> Self {
self.include_module_path = false;
self
}
pub fn base_level(mut self, b: log::Level) -> Self {
self.offset = match b {
log::Level::Error => 0,
log::Level::Warn => 1,
log::Level::Info => 2,
log::Level::Debug => 3,
log::Level::Trace => 4,
};
self
}
pub fn output(mut self, l: &log::Level, o: Output) -> Self {
match *l {
log::Level::Error => self.error.output = o,
log::Level::Warn => self.warn.output = o,
log::Level::Info => self.info.output = o,
log::Level::Debug => self.debug.output = o,
log::Level::Trace => self.trace.output = o,
}
self
}
pub fn verbosity(mut self, v: u64) -> Self {
self.verbosity = Some(v);
self
}
pub fn init(mut self) -> Result<(), SetLoggerError> {
if !self.include_level && !self.include_line_numbers && !self.include_module_path {
self.separator = String::new();
}
if let Some(v) = self.verbosity {
self.level = match v + self.offset {
0 => log::Level::Error,
1 => log::Level::Warn,
2 => log::Level::Info,
3 => log::Level::Debug,
_ => log::Level::Trace,
};
}
log::set_max_level(self.level.to_level_filter());
log::set_boxed_logger(Box::new(self))
}
fn select_color(&self, l: &log::Level) -> Colour {
match *l {
log::Level::Error => self.error.color,
log::Level::Warn => self.warn.color,
log::Level::Info => self.info.color,
log::Level::Debug => self.debug.color,
log::Level::Trace => self.trace.color,
}
}
fn select_output(&self, l: &log::Level) -> Output {
match *l {
log::Level::Error => self.error.output,
log::Level::Warn => self.warn.output,
log::Level::Info => self.info.output,
log::Level::Debug => self.debug.output,
log::Level::Trace => self.trace.output,
}
}
fn create_tag(&self, record: &log::Record) -> String {
let level = record.level();
let level_text = if self.include_level {
level.to_string()
} else {
String::new()
};
let module_path_text = if self.include_module_path {
let pth = record.module_path().unwrap_or("unknown");
if self.include_level {
format!(" [{}]", pth)
} else {
pth.into()
}
} else {
String::new()
};
let line_text = if self.include_line_numbers {
if let Some(l) = record.line() {
format!(" (line {})", l)
} else {
String::new()
}
} else {
String::new()
};
let mut tag = format!("{}{}{}", level_text, module_path_text, line_text);
if self.colors {
tag = self.select_color(&level).paint(tag).to_string();
}
tag
}
}
impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
match self.select_output(&record.level()) {
Output::Stderr => {
writeln!(
&mut io::stderr(),
"{}{}{}",
self.create_tag(&record),
self.separator,
record.args()
).expect("Writing to stderr");
},
Output::Stdout => {
println!(
"{}{}{}",
self.create_tag(&record),
self.separator,
record.args()
);
},
}
}
}
fn flush(&self) {
}
}
impl Default for Logger {
fn default() -> Logger {
Logger::new()
}
}
pub fn init_with_level(level: log::Level) -> Result<(), SetLoggerError> {
Logger::new().max_level(level).init()
}
pub fn init_with_verbosity(verbosity: u64) -> Result<(), SetLoggerError> {
Logger::new().verbosity(verbosity).init()
}
pub fn init_quiet() -> Result<(), SetLoggerError> {
init_with_level(log::Level::Warn)
}
#[cfg(test)]
mod tests {
use log;
use ansi_term::Colour;
use super::*;
#[test]
fn defaults_are_correct() {
let logger = Logger::new();
assert_eq!(logger.include_level, DEFAULT_INCLUDE_LEVEL);
assert_eq!(logger.include_line_numbers, DEFAULT_INCLUDE_LINE_NUMBERS);
assert_eq!(logger.include_module_path, DEFAULT_INCLUDE_MODULE_PATH);
assert_eq!(logger.colors, DEFAULT_COLORS);
assert_eq!(logger.level, DEFAULT_LEVEL);
assert_eq!(logger.separator, String::from(DEFAULT_SEPARATOR));
assert_eq!(logger.error.color, DEFAULT_ERROR_COLOR);
assert_eq!(logger.warn.color, DEFAULT_WARN_COLOR);
assert_eq!(logger.info.color, DEFAULT_INFO_COLOR);
assert_eq!(logger.debug.color, DEFAULT_DEBUG_COLOR);
assert_eq!(logger.trace.color, DEFAULT_TRACE_COLOR);
}
#[test]
fn color_works() {
let logger = Logger::new().color(&log::Level::Trace, Colour::Fixed(11));
assert_eq!(logger.trace.color, Colour::Fixed(11));
}
#[test]
fn separator_works() {
const EXPECTED: &str = " = ";
let logger = Logger::new().separator(EXPECTED);
assert_eq!(logger.separator, EXPECTED);
}
#[test]
fn colors_works() {
let logger = Logger::new().colors(false);
assert!(!logger.colors);
}
#[test]
fn no_colors_works() {
let logger = Logger::new().no_colors();
assert!(!logger.colors);
}
#[test]
fn line_numbers_works() {
let logger = Logger::new().line_numbers(true);
assert!(logger.include_line_numbers);
}
#[test]
fn level_works() {
let logger = Logger::new().level(true);
assert!(logger.include_level);
}
#[test]
fn max_level_works() {
let logger = Logger::new().max_level(log::Level::Trace);
assert_eq!(logger.level, log::Level::Trace);
assert!(logger.verbosity.is_none());
}
#[test]
fn base_level_works() {
let logger = Logger::new().base_level(log::Level::Info);
assert_eq!(logger.offset, 2);
}
#[test]
fn module_path_works() {
let logger = Logger::new().module_path(false);
assert!(!logger.include_module_path);
}
#[test]
fn no_module_path_works() {
let logger = Logger::new().no_module_path();
assert!(!logger.include_module_path);
}
#[test]
fn verbosity_works() {
let logger = Logger::new().verbosity(3);
assert_eq!(logger.verbosity, Some(3));
}
#[test]
fn output_works() {
let logger = Logger::new()
.output(&log::Level::Error, Output::Stdout)
.output(&log::Level::Warn, Output::Stdout)
.output(&log::Level::Info, Output::Stderr)
.output(&log::Level::Debug, Output::Stderr)
.output(&log::Level::Trace, Output::Stderr);
assert_eq!(logger.error.output, Output::Stdout);
assert_eq!(logger.warn.output, Output::Stdout);
assert_eq!(logger.info.output, Output::Stderr);
assert_eq!(logger.debug.output, Output::Stderr);
assert_eq!(logger.trace.output, Output::Stderr);
}
#[test]
fn init_works() {
let result = Logger::new().init();
assert!(result.is_ok());
}
#[test]
fn select_color_works() {
let logger = Logger::new();
assert_eq!(logger.select_color(&log::Level::Error), DEFAULT_ERROR_COLOR);
assert_eq!(logger.select_color(&log::Level::Warn), DEFAULT_WARN_COLOR);
assert_eq!(logger.select_color(&log::Level::Info), DEFAULT_INFO_COLOR);
assert_eq!(logger.select_color(&log::Level::Debug), DEFAULT_DEBUG_COLOR);
assert_eq!(logger.select_color(&log::Level::Trace), DEFAULT_TRACE_COLOR);
}
}