use std::fmt;
use std::panic::{self, AssertUnwindSafe};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LogLevel {
Debug,
Info,
Warn,
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
};
f.write_str(s)
}
}
#[derive(Debug, Clone)]
pub struct Log {
timestamp_ms: u64,
level: LogLevel,
tag: &'static str,
message: String,
}
impl Log {
pub fn new(level: LogLevel, tag: &'static str, message: impl Into<String>) -> Self {
let timestamp_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
Self {
timestamp_ms,
level,
tag,
message: message.into(),
}
}
pub fn debug(tag: &'static str, message: impl Into<String>) -> Self {
Self::new(LogLevel::Debug, tag, message)
}
pub fn timestamp_ms(&self) -> u64 {
self.timestamp_ms
}
pub fn level(&self) -> LogLevel {
self.level
}
pub fn tag(&self) -> &'static str {
self.tag
}
pub fn message(&self) -> &str {
&self.message
}
pub fn into_message(self) -> String {
self.message
}
}
impl fmt::Display for Log {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{}] {} [{}] {}",
self.timestamp_ms, self.level, self.tag, self.message
)
}
}
pub type DebugLogListener = Arc<dyn Fn(Log) + Send + Sync + 'static>;
static DEBUG_LOG_LISTENER: OnceLock<Mutex<Option<DebugLogListener>>> = OnceLock::new();
fn debug_log_listener_slot() -> &'static Mutex<Option<DebugLogListener>> {
DEBUG_LOG_LISTENER.get_or_init(|| Mutex::new(None))
}
#[inline]
pub fn debug_log_listener_active() -> bool {
match debug_log_listener_slot().lock() {
Ok(g) => g.is_some(),
Err(_) => false,
}
}
pub fn set_debug_log_listener(
listener: Option<DebugLogListener>,
) -> Result<(), DebugLogListenerError> {
let mut g = debug_log_listener_slot()
.lock()
.map_err(|_| DebugLogListenerError(()))?;
*g = listener;
Ok(())
}
pub fn try_set_debug_log_listener<F>(f: F) -> Result<(), DebugLogListenerError>
where
F: Fn(Log) + Send + Sync + 'static,
{
let mut g = debug_log_listener_slot()
.lock()
.map_err(|_| DebugLogListenerError(()))?;
if g.is_some() {
return Err(DebugLogListenerError(()));
}
*g = Some(Arc::new(f));
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DebugLogListenerError(());
impl fmt::Display for DebugLogListenerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("debug log listener already set")
}
}
impl std::error::Error for DebugLogListenerError {}
pub fn emit(log: Log) {
let cb_opt = debug_log_listener_slot()
.lock()
.ok()
.and_then(|g| g.as_ref().map(Arc::clone));
let Some(cb) = cb_opt else {
return;
};
let _ = panic::catch_unwind(AssertUnwindSafe(move || {
cb(log);
}));
}
#[inline]
pub fn emit_lazy<F>(f: F)
where
F: FnOnce() -> Log,
{
if !debug_log_listener_active() {
return;
}
emit(f());
}
#[macro_export]
macro_rules! meow_flow_log {
($tag:expr, $($arg:tt)*) => {
$crate::log::emit_lazy(|| {
$crate::log::Log::debug($tag, format!($($arg)*))
});
};
}