Skip to main content

rusty_cat/
log.rs

1//! 流程性调试日志:全局至多注册一个监听器;未注册时 `emit` 路径为低开销快速返回。
2//! 监听器调用包在 `catch_unwind` 中,避免用户回调 `panic!` 影响 SDK 内部逻辑。
3
4use std::fmt;
5use std::panic::{self, AssertUnwindSafe};
6use std::sync::{Arc, Mutex, OnceLock};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9/// 与流程日志条目一同输出的级别(便于外部过滤或映射到 tracing 等)。
10#[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/// 一条可交给外部打印或持久化的日志记录。
29#[derive(Debug, Clone)]
30pub struct Log {
31    /// 毫秒时间戳(Unix epoch),便于外部序列化。
32    timestamp_ms: u64,
33    level: LogLevel,
34    /// 固定标签,如 `"meow_client"`、`"enqueue"`,便于过滤。
35    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/// 是否已注册调试日志监听器(热路径上可先读此再决定是否构造 `Log`)。
97#[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
105/// 设置(或清空)全局调试日志监听器。
106///
107/// - `Some(listener)`: 设置/替换当前监听器
108/// - `None`: 清空监听器(等价于取消注册)
109pub 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
119/// 注册全局唯一的调试日志监听器;重复注册返回 `Err`。
120pub 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
145/// 发出一条日志;无监听器时立即返回。监听器 `panic` 会被捕获并丢弃。
146pub 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/// 仅当已注册监听器时才执行闭包构造 `Log`,避免无监听器时的字符串格式化开销。
160#[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/// 内部流程日志宏:无监听器时不展开 `format!`。
172/// crate::meow_flow_log!(
173///     "enqueue",
174///    "task_id={:?} offset={} total={}",
175///     task_id,
176///     offset,
177///    total
178/// );
179#[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}