use std::io::{self, Write};
use std::sync::Mutex;
use log::{Level, LevelFilter, Log};
const DEFAULT_LOG_LEVEL: Level = Level::Warn;
fn default_log_dest_for_level(level: Level) -> LogDestination<'static> {
match level {
Level::Error | Level::Warn => LogDestination::from(io::stderr()),
Level::Info | Level::Debug | Level::Trace => LogDestination::from(io::stdout()),
}
}
const fn level_into_level_filter(level: Level) -> LevelFilter {
match level {
Level::Error => LevelFilter::Error,
Level::Warn => LevelFilter::Warn,
Level::Info => LevelFilter::Info,
Level::Debug => LevelFilter::Debug,
Level::Trace => LevelFilter::Trace,
}
}
#[derive(Debug)]
#[allow(unused)]
pub enum Error {
Initialization,
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Initialization => writeln!(f, "unable to initialize logger"),
}
}
}
type LogDestinationWriter<'a> = Box<dyn Write + Send + Sync + 'a>;
type LockableLogDestinationWriter<'a> = Mutex<LogDestinationWriter<'a>>;
#[allow(unused)]
enum LogDestination<'data> {
Single(LockableLogDestinationWriter<'data>),
Multi(Vec<LockableLogDestinationWriter<'data>>),
}
impl<'data> LogDestination<'data> {
#[allow(unused)]
fn push<W>(self, writer: W) -> Self
where
W: Write + Send + Sync + Sized + 'data,
{
let dest = {
let boxed_dest = Box::new(writer) as LogDestinationWriter;
Mutex::new(boxed_dest)
};
match self {
LogDestination::Single(inner) => Self::Multi(vec![inner, dest]),
LogDestination::Multi(mut inner) => {
inner.push(dest);
Self::Multi(inner)
}
}
}
}
impl<'data, W> From<W> for LogDestination<'data>
where
W: Write + Send + Sync + Sized + 'data,
{
fn from(writer: W) -> Self {
let dest = {
let boxed_dest = Box::new(writer) as LogDestinationWriter;
Mutex::new(boxed_dest)
};
LogDestination::Single(dest)
}
}
pub struct LoggerBuilder<'data> {
logger: Logger<'data>,
}
impl<'data> LoggerBuilder<'data> {
const DEFAULT_LOG_LEVEL: Level = DEFAULT_LOG_LEVEL;
}
impl<'data> LoggerBuilder<'data> {
#[allow(unused)]
pub fn finalize(self) -> Result<Box<Logger<'data>>, Error> {
let boxed_logger = Box::new(self.logger);
Ok(boxed_logger)
}
fn set_error_dest(mut self, dest: LogDestination<'data>) -> Self {
self.logger.error = dest;
self
}
fn set_warn_dest(mut self, dest: LogDestination<'data>) -> Self {
self.logger.warn = dest;
self
}
fn set_info_dest(mut self, dest: LogDestination<'data>) -> Self {
self.logger.info = dest;
self
}
fn set_debug_dest(mut self, dest: LogDestination<'data>) -> Self {
self.logger.debug = dest;
self
}
fn set_trace_dest(mut self, dest: LogDestination<'data>) -> Self {
self.logger.trace = dest;
self
}
fn append_error_dest<W>(mut self, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
self.logger.error = self.logger.error.push(dest);
self
}
fn append_warn_dest<W>(mut self, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
self.logger.warn = self.logger.warn.push(dest);
self
}
fn append_info_dest<W>(mut self, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
self.logger.info = self.logger.info.push(dest);
self
}
fn append_debug_dest<W>(mut self, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
self.logger.debug = self.logger.debug.push(dest);
self
}
fn append_trace_dest<W>(mut self, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
self.logger.trace = self.logger.trace.push(dest);
self
}
pub fn output<W>(self, level: Level, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
let log_destination = LogDestination::from(dest);
match level {
Level::Error => self.set_error_dest(log_destination),
Level::Warn => self.set_warn_dest(log_destination),
Level::Info => self.set_info_dest(log_destination),
Level::Debug => self.set_debug_dest(log_destination),
Level::Trace => self.set_trace_dest(log_destination),
}
}
#[allow(unused)]
pub fn append_output<W>(self, level: Level, dest: W) -> Self
where
W: Write + Send + Sync + Sized + 'static,
{
match level {
Level::Error => self.append_error_dest(dest),
Level::Warn => self.append_warn_dest(dest),
Level::Info => self.append_info_dest(dest),
Level::Debug => self.append_debug_dest(dest),
Level::Trace => self.append_trace_dest(dest),
}
}
pub fn max_level(mut self, max_level: Level) -> Self {
self.logger.max_level = level_into_level_filter(max_level);
self
}
#[allow(unused)]
pub(crate) fn verbosity(mut self, verbosity: u8) -> Self {
let verbosity = verbosity as usize;
let offset = (DEFAULT_LOG_LEVEL as usize) + verbosity;
let adjusted_max_level = match offset {
0 => unreachable!(),
1 => Level::Error,
2 => Level::Warn,
3 => Level::Info,
4 => Level::Debug,
_ => Level::Trace,
};
let adjusted_max_level_filter = level_into_level_filter(adjusted_max_level);
self.logger.max_level = adjusted_max_level_filter;
self
}
}
impl Default for LoggerBuilder<'static> {
fn default() -> Self {
let logger = Logger {
max_level: level_into_level_filter(Self::DEFAULT_LOG_LEVEL),
error: default_log_dest_for_level(Level::Error),
warn: default_log_dest_for_level(Level::Warn),
info: default_log_dest_for_level(Level::Info),
debug: default_log_dest_for_level(Level::Debug),
trace: default_log_dest_for_level(Level::Trace),
};
LoggerBuilder { logger }
}
}
#[allow(unused)]
pub struct Logger<'data> {
max_level: LevelFilter,
error: LogDestination<'data>,
warn: LogDestination<'data>,
info: LogDestination<'data>,
debug: LogDestination<'data>,
trace: LogDestination<'data>,
}
impl<'data> Logger<'data> {
fn as_logdestination_from_level(&self, level: Level) -> &LogDestination<'data> {
match level {
Level::Error => &self.error,
Level::Warn => &self.warn,
Level::Info => &self.info,
Level::Debug => &self.debug,
Level::Trace => &self.trace,
}
}
#[allow(unused)]
pub fn max_level_filter(&self) -> LevelFilter {
self.max_level
}
}
impl<'data> Log for Logger<'data> {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.max_level
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let record_level = record.level();
let level_oriented_log_destination = self.as_logdestination_from_level(record_level);
match level_oriented_log_destination {
LogDestination::Single(writer) => {
if let Ok(mut log_writer) = writer.lock() {
let _ = writeln!(log_writer, "{}", record.args());
}
}
LogDestination::Multi(writers) => {
let lockable_writers = writers
.iter()
.flat_map(|lockable_writer| lockable_writer.lock());
for mut log_writer in lockable_writers {
let _ = writeln!(log_writer, "{}", record.args());
}
}
}
}
fn flush(&self) {}
}
#[cfg(test)]
mod tests {}