use chrono::Local;
use std::fmt::Write as FmtWrite;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
None = 4,
}
pub struct MID_Logger {
current_level: LogLevel,
indentation_level: usize,
is_enabled: bool,
log_buffer: String,
}
impl MID_Logger {
const INDENTATION_SPACES: usize = 2;
pub fn new(level: LogLevel, enabled: bool) -> Self {
MID_Logger {
current_level: level,
indentation_level: 0,
is_enabled: enabled,
log_buffer: String::new(),
}
}
pub fn GetSharedInstance(level: Option<LogLevel>, enabled: Option<bool>) -> Arc<Mutex<Self>> {
use std::sync::OnceLock;
static SHARED: OnceLock<Arc<Mutex<MID_Logger>>> = OnceLock::new();
let instance = SHARED.get_or_init(|| {
Arc::new(Mutex::new(MID_Logger::new(
level.unwrap_or(LogLevel::Info),
enabled.unwrap_or(true),
)))
});
if let (Some(lvl), Some(en)) = (level, enabled) {
if let Ok(mut logger) = instance.lock() {
logger.SetLogLevel(lvl);
logger.SetEnabled(en);
}
}
instance.clone()
}
pub fn HasSharedInstance() -> bool {
use std::sync::OnceLock;
static SHARED: OnceLock<Arc<Mutex<MID_Logger>>> = OnceLock::new();
SHARED.get().is_some()
}
pub fn IncreaseIndent(&mut self) {
self.indentation_level += 1;
}
pub fn DecreaseIndent(&mut self) {
if self.indentation_level > 0 {
self.indentation_level -= 1;
}
}
pub fn ResetIndent(&mut self) {
self.indentation_level = 0;
}
pub fn Error(&mut self, message: &str) {
if !self.is_enabled {
return;
}
self.log_internal(message, LogLevel::Error);
}
pub fn Warning(&mut self, message: &str) {
if !self.is_enabled {
return;
}
self.log_internal(message, LogLevel::Warning);
}
pub fn Info(&mut self, message: &str) {
if !self.is_enabled {
return;
}
if self.current_level > LogLevel::Info {
return;
}
self.log_internal(message, LogLevel::Info);
}
#[inline]
pub fn Debug<F>(&mut self, message_builder: F)
where
F: FnOnce() -> String,
{
if !self.is_enabled {
return;
}
if self.current_level > LogLevel::Debug {
return;
}
let message = message_builder();
self.log_internal(&message, LogLevel::Debug);
}
#[inline]
pub fn DebugStr(&mut self, message: &str) {
if !self.is_enabled {
return;
}
if self.current_level > LogLevel::Debug {
return;
}
self.log_internal(message, LogLevel::Debug);
}
#[inline]
pub fn Verbose<F>(&mut self, message_builder: F)
where
F: FnOnce() -> String,
{
if !self.is_enabled {
return;
}
if self.current_level > LogLevel::Debug {
return;
}
let message = format!("[VERBOSE] {}", message_builder());
self.log_internal(&message, LogLevel::Debug);
}
#[inline]
pub fn VerboseStr(&mut self, message: &str) {
if !self.is_enabled {
return;
}
if self.current_level > LogLevel::Debug {
return;
}
let message = format!("[VERBOSE] {}", message);
self.log_internal(&message, LogLevel::Debug);
}
#[inline]
fn log_internal(&mut self, message: &str, level: LogLevel) {
let indentation = " ".repeat(self.indentation_level * Self::INDENTATION_SPACES);
let timestamp = Local::now().format("%H:%M:%S%.3f");
let formatted_message = format!("[{}] {}[{:?}] {}", timestamp, indentation, level, message);
Self::write_to_console(&formatted_message, level);
writeln!(self.log_buffer, "{}", formatted_message).ok();
}
#[inline]
fn write_to_console(message: &str, level: LogLevel) {
match level {
LogLevel::Debug => println!("\x1b[90m{}\x1b[0m", message), LogLevel::Info => println!("\x1b[97m{}\x1b[0m", message), LogLevel::Warning => println!("\x1b[93m{}\x1b[0m", message), LogLevel::Error => println!("\x1b[91m{}\x1b[0m", message), LogLevel::None => {}
}
}
pub fn CreateScope<'a>(&'a mut self, scope_name: &str) -> LoggerScope<'a> {
if !self.is_enabled {
return LoggerScope::null();
}
if self.current_level <= LogLevel::Info {
self.log_internal(&format!("▶ {}", scope_name), LogLevel::Info);
}
self.IncreaseIndent();
LoggerScope {
logger: Some(self),
scope_name: scope_name.to_string(),
is_debug: false,
is_verbose: false,
}
}
pub fn CreateDebugScope<'a>(&'a mut self, scope_name: &str) -> LoggerScope<'a> {
if !self.is_enabled {
return LoggerScope::null();
}
if self.current_level <= LogLevel::Debug {
self.log_internal(&format!("▶ [DEBUG] {}", scope_name), LogLevel::Debug);
}
self.IncreaseIndent();
LoggerScope {
logger: Some(self),
scope_name: scope_name.to_string(),
is_debug: true,
is_verbose: false,
}
}
pub fn CreateVerboseScope<'a>(&'a mut self, scope_name: &str) -> LoggerScope<'a> {
if !self.is_enabled {
return LoggerScope::null();
}
if self.current_level <= LogLevel::Debug {
self.log_internal(&format!("▶ [VERBOSE] {}", scope_name), LogLevel::Debug);
}
self.IncreaseIndent();
LoggerScope {
logger: Some(self),
scope_name: scope_name.to_string(),
is_debug: false,
is_verbose: true,
}
}
fn end_scope(&mut self, scope_name: &str, is_debug: bool, is_verbose: bool) {
self.DecreaseIndent();
if !self.is_enabled {
return;
}
if is_verbose {
self.log_internal(&format!("◀ [VERBOSE] {}", scope_name), LogLevel::Debug);
} else if is_debug {
self.log_internal(&format!("◀ [DEBUG] {}", scope_name), LogLevel::Debug);
} else if self.current_level <= LogLevel::Info {
self.log_internal(&format!("◀ {}", scope_name), LogLevel::Info);
}
}
pub fn SetLogLevel(&mut self, level: LogLevel) {
self.current_level = level;
}
pub fn SetEnabled(&mut self, enabled: bool) {
self.is_enabled = enabled;
}
pub fn GetCurrentLevel(&self) -> LogLevel {
self.current_level
}
pub fn IsEnabled(&self) -> bool {
self.is_enabled
}
#[inline]
pub fn IsDebugEnabled(&self) -> bool {
#[cfg(feature = "debug_logging")]
{
self.is_enabled && self.current_level <= LogLevel::Debug
}
#[cfg(not(feature = "debug_logging"))]
{
false
}
}
#[inline]
pub fn IsVerboseEnabled(&self) -> bool {
#[cfg(feature = "verbose_logging")]
{
self.is_enabled && self.current_level <= LogLevel::Debug
}
#[cfg(not(feature = "verbose_logging"))]
{
false
}
}
pub fn WouldLog(&self, level: LogLevel) -> bool {
self.is_enabled && level >= self.current_level
}
pub fn GetLogContents(&self) -> &str {
&self.log_buffer
}
pub fn ClearLogBuffer(&mut self) {
self.log_buffer.clear();
}
}
pub struct LoggerScope<'a> {
logger: Option<&'a mut MID_Logger>,
scope_name: String,
is_debug: bool,
is_verbose: bool,
}
impl<'a> LoggerScope<'a> {
fn null() -> Self {
LoggerScope {
logger: None,
scope_name: String::new(),
is_debug: false,
is_verbose: false,
}
}
pub fn dismiss(mut self) {
self.logger = None;
}
}
impl<'a> Drop for LoggerScope<'a> {
fn drop(&mut self) {
if let Some(logger) = self.logger.as_mut() {
logger.end_scope(&self.scope_name, self.is_debug, self.is_verbose);
}
}
}