use std::{
fs::{File, OpenOptions},
io::Write,
path::{Path, PathBuf},
sync::Mutex,
};
use anyhow::Context as _;
use chrono::Local;
use log::{Level, LevelFilter, Log, Metadata, Record};
pub struct FileLogger {
file: Mutex<File>,
}
impl FileLogger {
fn new(log_path: &Path) -> std::io::Result<Self> {
if let Some(parent) = log_path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = OpenOptions::new()
.create(true)
.append(true)
.open(log_path)?;
Ok(Self {
file: Mutex::new(file),
})
}
}
impl Log for FileLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
true
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
let level_str = match record.level() {
Level::Error => "ERROR",
Level::Warn => "WARN ",
Level::Info => "INFO ",
Level::Debug => "DEBUG",
Level::Trace => "TRACE",
};
let message = format!("[{} {}] {}\n", level_str, timestamp, record.args());
if let Ok(mut file) = self.file.lock() {
let _ = file.write_all(message.as_bytes());
}
}
}
fn flush(&self) {
if let Ok(mut file) = self.file.lock() {
let _ = file.flush();
}
}
}
pub fn log_file_path(workspace_root: &Path) -> PathBuf {
workspace_root.join("target").join("ostool.ans")
}
pub fn init_file_logger(workspace_root: &Path) -> anyhow::Result<PathBuf> {
let log_path = log_file_path(workspace_root);
let logger = FileLogger::new(&log_path)
.with_context(|| format!("failed to create log file: {}", log_path.display()))?;
log::set_boxed_logger(Box::new(logger)).context("failed to install ostool file logger")?;
log::set_max_level(LevelFilter::Debug);
Ok(log_path)
}