use std::{
fs::{OpenOptions, create_dir_all},
io::{BufWriter, Write},
path::PathBuf,
sync::Mutex,
time::Duration,
};
use humantime::{FormattedDuration, format_duration};
use log::{LevelFilter, Metadata, Record, SetLoggerError};
use vexide::time::user_uptime;
#[derive(Debug)]
pub struct AntLogger {
file_writer: Mutex<Option<BufWriter<std::fs::File>>>,
}
impl AntLogger {
fn new() -> Self {
let file_writer = Self::log_path()
.and_then(|path| {
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
create_dir_all(parent).ok()?;
}
}
OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.ok()
})
.map(BufWriter::new);
Self {
file_writer: Mutex::new(file_writer),
}
}
fn log_path() -> Option<PathBuf> {
if cfg!(target_os = "vexos") {
Some(PathBuf::from("log.txt"))
} else {
Some(PathBuf::from("test-artifacts/log_test.txt"))
}
}
}
impl log::Log for AntLogger {
fn enabled(&self, metadata: &Metadata<'_>) -> bool { metadata.level() <= log::max_level() }
fn log(&self, record: &Record<'_>) {
if self.enabled(record.metadata()) {
let log_line = format!(
"{} [{}] {} - {}\n",
record.level(),
get_time(),
record.target(),
record.args()
);
print!("{}", log_line);
if let Ok(mut writer_guard) = self.file_writer.lock() {
if let Some(ref mut writer) = *writer_guard {
let _ = writer.write_all(log_line.as_bytes());
}
}
}
}
fn flush(&self) {
if let Ok(mut writer_guard) = self.file_writer.lock() {
if let Some(ref mut writer) = *writer_guard {
let _ = writer.flush();
}
}
}
}
static LOGGER: std::sync::OnceLock<AntLogger> = std::sync::OnceLock::new();
pub fn init(level: LevelFilter) -> Result<(), SetLoggerError> {
let logger = LOGGER.get_or_init(|| AntLogger::new());
log::set_logger(logger).map(|()| log::set_max_level(level))
}
fn get_time() -> FormattedDuration {
let dur;
if !cfg!(target_os = "vexos") {
dur = Duration::from_millis(123432);
} else {
dur = user_uptime();
}
format_duration(dur)
}
#[cfg(test)]
mod tests {
use log::{LevelFilter, debug, error, info, trace, warn};
#[test]
fn log_full_test() {
super::init(LevelFilter::Trace).expect("Failed to initialize logger");
trace!("This is a trace message");
debug!("This is a debug message");
info!("This is an info message");
warn!("This is a warning message");
error!("This is an error message");
log::logger().flush();
assert!(
log::logger().enabled(
&log::Metadata::builder()
.level(log::Level::Error)
.target("test")
.build()
)
);
}
}