use std::cell::Cell;
use crate::time::sim_time_ns;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
Critical = 4,
Off = 5,
}
impl Level {
fn as_str(self) -> &'static str {
match self {
Level::Debug => "DEBUG",
Level::Info => "INFO",
Level::Warning => "WARNING",
Level::Error => "ERROR",
Level::Critical => "CRITICAL",
Level::Off => "OFF",
}
}
}
thread_local! {
static THRESHOLD: Cell<Level> = const { Cell::new(Level::Info) };
}
pub fn set_level(l: Level) {
THRESHOLD.with(|t| t.set(l));
}
pub fn log(level: Level, msg: &str) {
let enabled = THRESHOLD.with(|t| level >= t.get());
if enabled {
emit(&format!("{:>10.2}ns {:<8} {}", sim_time_ns(), level.as_str(), msg));
}
}
pub fn debug(msg: &str) {
log(Level::Debug, msg);
}
pub fn info(msg: &str) {
log(Level::Info, msg);
}
pub fn warning(msg: &str) {
log(Level::Warning, msg);
}
pub fn error(msg: &str) {
log(Level::Error, msg);
}
pub fn critical(msg: &str) {
log(Level::Critical, msg);
}
use std::cell::RefCell;
use std::io::Write;
use std::rc::Rc;
fn under(path: &str, prefix: &str) -> bool {
crate::path::str_is_under(path, prefix)
}
thread_local! {
static TARGET_LEVELS: RefCell<Vec<(String, Level)>> = const { RefCell::new(Vec::new()) };
static LOG_FILE: RefCell<Option<std::fs::File>> = const { RefCell::new(None) };
static TARGET_FILES: RefCell<Vec<(String, Rc<RefCell<std::fs::File>>)>> =
const { RefCell::new(Vec::new()) };
static TARGET_CONSOLE: RefCell<Vec<(String, bool)>> = const { RefCell::new(Vec::new()) };
}
pub fn set_level_for(path_prefix: &str, l: Level) {
TARGET_LEVELS.with(|t| {
let mut v = t.borrow_mut();
v.retain(|(p, _)| p != path_prefix);
v.push((path_prefix.to_string(), l));
});
}
pub fn log_to_file(path: &str, append: bool) -> std::io::Result<()> {
let file = open_log(path, append)?;
LOG_FILE.with(|f| *f.borrow_mut() = Some(file));
Ok(())
}
pub fn remove_log_file() {
LOG_FILE.with(|f| *f.borrow_mut() = None);
}
fn open_log(path: &str, append: bool) -> std::io::Result<std::fs::File> {
std::fs::OpenOptions::new()
.create(true)
.write(true)
.append(append)
.truncate(!append)
.open(path)
}
pub fn add_file_for(path_prefix: &str, path: &str, append: bool) -> std::io::Result<()> {
let file = Rc::new(RefCell::new(open_log(path, append)?));
TARGET_FILES.with(|t| t.borrow_mut().push((path_prefix.to_string(), file)));
Ok(())
}
pub fn set_console_for(path_prefix: &str, enabled: bool) {
TARGET_CONSOLE.with(|t| {
let mut v = t.borrow_mut();
v.retain(|(p, _)| p != path_prefix);
v.push((path_prefix.to_string(), enabled));
});
}
pub fn reset_config() {
THRESHOLD.with(|t| t.set(Level::Info));
TARGET_LEVELS.with(|t| t.borrow_mut().clear());
TARGET_FILES.with(|t| t.borrow_mut().clear());
TARGET_CONSOLE.with(|t| t.borrow_mut().clear());
LOG_FILE.with(|f| *f.borrow_mut() = None);
}
fn console_enabled_for(path: &str) -> bool {
TARGET_CONSOLE
.with(|t| {
t.borrow()
.iter()
.filter(|(p, _)| under(path, p))
.max_by_key(|(p, _)| p.len())
.map(|(_, on)| *on)
})
.unwrap_or(true)
}
fn emit_for(path: &str, line: &str) {
if console_enabled_for(path) {
println!("{line}");
}
TARGET_FILES.with(|t| {
for (prefix, file) in t.borrow().iter() {
if under(path, prefix) {
let _ = writeln!(file.borrow_mut(), "{line}");
}
}
});
LOG_FILE.with(|f| {
if let Some(file) = f.borrow_mut().as_mut() {
let _ = writeln!(file, "{line}");
}
});
}
fn emit(line: &str) {
println!("{line}");
LOG_FILE.with(|f| {
if let Some(file) = f.borrow_mut().as_mut() {
let _ = writeln!(file, "{line}");
}
});
}
#[derive(Clone)]
pub struct Logger {
path: crate::path::RustdvPath,
}
impl Logger {
pub fn new(path: &str) -> Logger {
let mut p = crate::path::RustdvPath::empty();
if !path.is_empty() {
for seg in path.split('.') {
p = p.child(seg);
}
}
Logger { path: p }
}
pub fn at(path: crate::path::RustdvPath) -> Logger {
Logger { path }
}
pub fn path(&self) -> &str {
self.path.as_str()
}
pub fn rustdv_path(&self) -> &crate::path::RustdvPath {
&self.path
}
fn enabled(&self, level: Level) -> bool {
let per_target = TARGET_LEVELS.with(|t| {
t.borrow()
.iter()
.filter(|(p, _)| under(self.path.as_str(), p))
.max_by_key(|(p, _)| p.len())
.map(|(_, l)| *l)
});
match per_target {
Some(l) => level >= l,
None => THRESHOLD.with(|t| level >= t.get()),
}
}
pub fn log(&self, level: Level, msg: &str) {
if self.enabled(level) {
emit_for(
self.path.as_str(),
&format!(
"{:>10.2}ns {:<8} [{}]: {}",
sim_time_ns(),
level.as_str(),
self.path,
msg
),
);
}
}
pub fn debug(&self, msg: &str) {
self.log(Level::Debug, msg);
}
pub fn info(&self, msg: &str) {
self.log(Level::Info, msg);
}
pub fn warning(&self, msg: &str) {
self.log(Level::Warning, msg);
}
pub fn error(&self, msg: &str) {
self.log(Level::Error, msg);
}
pub fn critical(&self, msg: &str) {
self.log(Level::Critical, msg);
}
}