crunchy-cli/crunchy-cli-core/src/cli/log.rs
2022-11-24 15:30:49 +01:00

197 lines
5.8 KiB
Rust

use log::{
set_boxed_logger, set_max_level, Level, LevelFilter, Log, Metadata, Record, SetLoggerError,
};
use std::io::{stdout, Write};
use std::sync::{mpsc, Mutex};
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
struct CliProgress {
handler: JoinHandle<()>,
sender: mpsc::SyncSender<(String, Level)>,
}
impl CliProgress {
fn new(record: &Record) -> Self {
let (tx, rx) = mpsc::sync_channel(1);
let init_message = format!("{}", record.args());
let init_level = record.level();
let handler = thread::spawn(move || {
let states = ["-", "\\", "|", "/"];
let mut old_message = init_message.clone();
let mut latest_info_message = init_message;
let mut old_level = init_level;
for i in 0.. {
let (msg, level) = match rx.try_recv() {
Ok(payload) => payload,
Err(e) => match e {
mpsc::TryRecvError::Empty => (old_message.clone(), old_level),
mpsc::TryRecvError::Disconnected => break,
},
};
// clear last line
// prefix (2), space (1), state (1), space (1), message(n)
let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len()));
if old_level != level || old_message != msg {
if old_level <= Level::Warn {
let _ = writeln!(stdout(), "\r:: • {}", old_message);
} else if old_level == Level::Info && level <= Level::Warn {
let _ = writeln!(stdout(), "\r:: → {}", old_message);
} else if level == Level::Info {
latest_info_message = msg.clone();
}
}
let _ = write!(
stdout(),
"\r:: {} {}",
states[i / 2 % states.len()],
if level == Level::Info {
&msg
} else {
&latest_info_message
}
);
let _ = stdout().flush();
old_message = msg;
old_level = level;
thread::sleep(Duration::from_millis(100));
}
// clear last line
// prefix (2), space (1), state (1), space (1), message(n)
let _ = write!(stdout(), "\r {}", " ".repeat(old_message.len()));
let _ = writeln!(stdout(), "\r:: ✓ {}", old_message);
let _ = stdout().flush();
});
Self {
handler,
sender: tx,
}
}
fn send(&self, record: &Record) {
let _ = self
.sender
.send((format!("{}", record.args()), record.level()));
}
fn stop(self) {
drop(self.sender);
let _ = self.handler.join();
}
}
#[allow(clippy::type_complexity)]
pub struct CliLogger {
level: LevelFilter,
progress: Mutex<Option<CliProgress>>,
}
impl Log for CliLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata())
|| (record.target() != "progress"
&& record.target() != "progress_end"
&& !record.target().starts_with("crunchy_cli"))
{
return;
}
if self.level >= LevelFilter::Debug {
self.extended(record);
return;
}
match record.target() {
"progress" => self.progress(record, false),
"progress_end" => self.progress(record, true),
_ => {
if self.progress.lock().unwrap().is_some() {
self.progress(record, false);
} else if record.level() > Level::Warn {
self.normal(record)
} else {
self.error(record)
}
}
}
}
fn flush(&self) {
let _ = stdout().flush();
}
}
impl CliLogger {
pub fn new(level: LevelFilter) -> Self {
Self {
level,
progress: Mutex::new(None),
}
}
pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> {
set_max_level(level);
set_boxed_logger(Box::new(CliLogger::new(level)))
}
fn extended(&self, record: &Record) {
println!(
"[{}] {} {} ({}) {}",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"),
record.level(),
// replace the 'progress' prefix if this function is invoked via 'progress!'
record
.target()
.replacen("progress", "crunchy_cli", 1)
.replacen("progress_end", "crunchy_cli", 1),
format!("{:?}", thread::current().id())
.replace("ThreadId(", "")
.replace(')', ""),
record.args()
)
}
fn normal(&self, record: &Record) {
println!(":: {}", record.args())
}
fn error(&self, record: &Record) {
eprintln!(":: {}", record.args())
}
fn progress(&self, record: &Record, stop: bool) {
let mut progress_option = self.progress.lock().unwrap();
if stop && progress_option.is_some() {
progress_option.take().unwrap().stop()
} else if let Some(p) = &*progress_option {
p.send(record);
} else {
*progress_option = Some(CliProgress::new(record))
}
}
}
macro_rules! tab_info {
($($arg:tt)+) => {
if log::max_level() == log::LevelFilter::Debug {
info!($($arg)+)
} else {
info!("\t{}", format!($($arg)+))
}
}
}
pub(crate) use tab_info;