use std::{borrow::Cow, sync::OnceLock};
use console::strip_ansi_codes;
use log::{Level, Metadata, Record, SetLoggerError};
use tokio::sync::mpsc::Sender;
static LOGGER: OnceLock<AppLogger> = OnceLock::new();
#[derive(Clone)]
enum LogOutput {
Stdout,
Sender(Sender<String>),
}
#[derive(Clone)]
struct AppLogger {
output: LogOutput,
includes_ansi_codes: bool,
}
impl log::Log for AppLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Info
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
match &self.output {
LogOutput::Stdout => {
println!("{}", record.args());
}
LogOutput::Sender(tx) => {
let args = record.args().to_string();
let msg = if !self.includes_ansi_codes {
strip_ansi_codes(args.as_ref())
} else {
Cow::from(args)
};
let level_initial = record
.level()
.to_string()
.chars()
.next()
.unwrap_or('?');
let msg_with_log_level = format!("[{}] {}", level_initial, msg);
let tx = tx.clone();
tokio::spawn(async move {
let _ = tx.send(msg_with_log_level).await;
});
}
}
}
fn flush(&self) {}
}
pub fn init_logger(
tx: Option<Sender<String>>,
includes_ansi_codes: bool,
) -> Result<(), SetLoggerError> {
let output = if let Some(tx) = tx {
LogOutput::Sender(tx)
} else {
LogOutput::Stdout
};
LOGGER
.set(AppLogger {
output,
includes_ansi_codes,
})
.ok();
let logger = LOGGER
.get()
.expect("logger must have been initialized by the set() above");
log::set_logger(logger)?;
log::set_max_level(log::LevelFilter::Info);
Ok(())
}