#![deny(missing_docs)]
extern crate ansi_term;
extern crate isatty;
extern crate log;
extern crate unicode_segmentation;
use std::cmp::max;
use std::io::{stderr, stdout, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use ansi_term::{ANSIGenericString, Colour, Style};
use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord, SetLoggerError};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Destination {
Stdout,
Stderr,
}
impl Destination {
pub fn isatty(&self) -> bool {
match *self {
Destination::Stdout => isatty::stdout_isatty(),
Destination::Stderr => isatty::stderr_isatty(),
}
}
}
impl Destination {
fn write(&self) -> Box<Write> {
match *self {
Destination::Stdout => Box::new(stdout()),
Destination::Stderr => Box::new(stderr()),
}
}
}
impl Default for Destination {
fn default() -> Destination {
Destination::Stderr
}
}
pub struct Logger {
destination: Destination,
level: LogLevelFilter,
max_module_width: AtomicUsize,
max_target_width: AtomicUsize,
theme: Theme,
}
impl Logger {
pub fn new(destination: Destination, level: LogLevelFilter, theme: Theme) -> Logger {
Logger {
destination,
level,
max_module_width: AtomicUsize::new(0),
max_target_width: AtomicUsize::new(0),
theme,
}
}
pub fn set_logger(self) -> Result<(), SetLoggerError> {
log::set_logger(|m| {
m.set(self.level);
Box::new(self)
})
}
fn update_module_width(&self, width: usize) -> usize {
loop {
let old = self.max_module_width.load(Ordering::SeqCst);
let new = max(old, width);
if self.max_module_width.compare_and_swap(old, new, Ordering::SeqCst) == old {
return new;
}
}
}
fn update_target_width(&self, width: usize) -> usize {
loop {
let old = self.max_target_width.load(Ordering::SeqCst);
let new = max(old, width);
if self.max_target_width.compare_and_swap(old, new, Ordering::SeqCst) == old {
return new;
}
}
}
}
impl Default for Logger {
fn default() -> Logger {
let destination = Destination::default();
let theme = if destination.isatty() {
Theme::default()
} else {
Theme::empty()
};
Logger::new(destination, LogLevelFilter::Info, theme)
}
}
impl Log for Logger {
fn enabled(&self, metadata: &LogMetadata) -> bool {
self.level.to_log_level().map(|level| {
metadata.level() <= level
}).unwrap_or(false)
}
fn log(&self, record: &LogRecord) {
if !self.enabled(record.metadata()) {
return;
}
let module = record.location().module_path();
let target = record.target();
let module_length = self.update_module_width(module.graphemes(true).count());
let _ = if module == target {
writeln!(self.destination.write(), "{}|{:.*}|{}",
self.theme.paint_log_level(record.level()), module_length,
module, record.args())
} else {
let target_length = self.update_target_width(target.graphemes(true).count());
writeln!(self.destination.write(), "{}|{:.*}|{:.*}|{}",
self.theme.paint_log_level(record.level()), module_length,
module, target_length, target, record.args())
};
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Theme {
pub error: Style,
pub warn: Style,
pub info: Style,
pub debug: Style,
pub trace: Style,
pub module: Style,
}
impl Theme {
pub fn empty() -> Theme {
Theme {
error: Style::new(),
warn: Style::new(),
info: Style::new(),
debug: Style::new(),
trace: Style::new(),
module: Style::new(),
}
}
pub fn paint_log_level(&self, level: LogLevel) -> ANSIGenericString<'static, str> {
let (style, name) = match level {
LogLevel::Error => (self.error, "ERROR"),
LogLevel::Warn => (self.warn, "WARN "),
LogLevel::Info => (self.info, "INFO "),
LogLevel::Debug => (self.debug, "DEBUG"),
LogLevel::Trace => (self.trace, "TRACE"),
};
style.paint(name)
}
}
impl Default for Theme {
fn default() -> Theme {
Theme {
error: Colour::Red.bold(),
warn: Colour::Yellow.bold(),
info: Colour::Cyan.normal(),
debug: Colour::White.normal(),
trace: Colour::White.dimmed(),
module: Style::new(),
}
}
}
pub fn init(destination: Destination, level: LogLevelFilter, theme: Theme) -> Result<(), SetLoggerError> {
platform_init();
Logger::new(destination, level, theme).set_logger()
}
pub fn init_level(level: LogLevelFilter) -> Result<(), SetLoggerError> {
platform_init();
let mut logger = Logger::default();
logger.level = level;
logger.set_logger()
}
pub fn init_to_defaults() -> Result<(), SetLoggerError> {
platform_init();
Logger::default().set_logger()
}
#[cfg(windows)]
fn platform_init() {
use ansi_term::enable_ansi_support;
let _ = enable_ansi_support();
}
#[cfg(not(windows))]
fn platform_init() {}