1use std::fmt;
5use std::panic::{self, AssertUnwindSafe};
6use std::sync::{Arc, Mutex, OnceLock};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum LogLevel {
12 Debug,
13 Info,
14 Warn,
15}
16
17impl fmt::Display for LogLevel {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 let s = match self {
20 LogLevel::Debug => "DEBUG",
21 LogLevel::Info => "INFO",
22 LogLevel::Warn => "WARN",
23 };
24 f.write_str(s)
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct Log {
31 timestamp_ms: u64,
33 level: LogLevel,
34 tag: &'static str,
36 message: String,
37}
38
39impl Log {
40 pub fn new(level: LogLevel, tag: &'static str, message: impl Into<String>) -> Self {
41 let timestamp_ms = SystemTime::now()
42 .duration_since(UNIX_EPOCH)
43 .map(|d| d.as_millis() as u64)
44 .unwrap_or(0);
45 Self {
46 timestamp_ms,
47 level,
48 tag,
49 message: message.into(),
50 }
51 }
52
53 pub fn debug(tag: &'static str, message: impl Into<String>) -> Self {
54 Self::new(LogLevel::Debug, tag, message)
55 }
56
57 pub fn timestamp_ms(&self) -> u64 {
58 self.timestamp_ms
59 }
60
61 pub fn level(&self) -> LogLevel {
62 self.level
63 }
64
65 pub fn tag(&self) -> &'static str {
66 self.tag
67 }
68
69 pub fn message(&self) -> &str {
70 &self.message
71 }
72
73 pub fn into_message(self) -> String {
74 self.message
75 }
76}
77
78impl fmt::Display for Log {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(
81 f,
82 "[{}] {} [{}] {}",
83 self.timestamp_ms, self.level, self.tag, self.message
84 )
85 }
86}
87
88pub type DebugLogListener = Arc<dyn Fn(Log) + Send + Sync + 'static>;
89
90static DEBUG_LOG_LISTENER: OnceLock<Mutex<Option<DebugLogListener>>> = OnceLock::new();
91
92fn debug_log_listener_slot() -> &'static Mutex<Option<DebugLogListener>> {
93 DEBUG_LOG_LISTENER.get_or_init(|| Mutex::new(None))
94}
95
96#[inline]
98pub fn debug_log_listener_active() -> bool {
99 match debug_log_listener_slot().lock() {
100 Ok(g) => g.is_some(),
101 Err(_) => false,
102 }
103}
104
105pub fn set_debug_log_listener(
110 listener: Option<DebugLogListener>,
111) -> Result<(), DebugLogListenerError> {
112 let mut g = debug_log_listener_slot()
113 .lock()
114 .map_err(|_| DebugLogListenerError(()))?;
115 *g = listener;
116 Ok(())
117}
118
119pub fn try_set_debug_log_listener<F>(f: F) -> Result<(), DebugLogListenerError>
121where
122 F: Fn(Log) + Send + Sync + 'static,
123{
124 let mut g = debug_log_listener_slot()
125 .lock()
126 .map_err(|_| DebugLogListenerError(()))?;
127 if g.is_some() {
128 return Err(DebugLogListenerError(()));
129 }
130 *g = Some(Arc::new(f));
131 Ok(())
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct DebugLogListenerError(());
136
137impl fmt::Display for DebugLogListenerError {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.write_str("debug log listener already set")
140 }
141}
142
143impl std::error::Error for DebugLogListenerError {}
144
145pub fn emit(log: Log) {
147 let cb_opt = debug_log_listener_slot()
148 .lock()
149 .ok()
150 .and_then(|g| g.as_ref().map(Arc::clone));
151 let Some(cb) = cb_opt else {
152 return;
153 };
154 let _ = panic::catch_unwind(AssertUnwindSafe(move || {
155 cb(log);
156 }));
157}
158
159#[inline]
161pub fn emit_lazy<F>(f: F)
162where
163 F: FnOnce() -> Log,
164{
165 if !debug_log_listener_active() {
166 return;
167 }
168 emit(f());
169}
170
171#[macro_export]
180macro_rules! meow_flow_log {
181 ($tag:expr, $($arg:tt)*) => {
182 $crate::log::emit_lazy(|| {
183 $crate::log::Log::debug($tag, format!($($arg)*))
184 });
185 };
186}