1extern crate alloc;
2
3use alloc::boxed::Box;
4use alloc::vec::Vec;
5use core::sync::atomic::{AtomicU8, Ordering};
6
7pub mod sinks;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
10#[repr(u8)]
11pub enum Level {
12 Error = 1,
13 Warn = 2,
14 Info = 3,
15 Debug = 4,
16 Trace = 5,
17}
18
19impl Level {
20 pub fn as_str(self) -> &'static str {
21 match self {
22 Level::Error => "ERROR",
23 Level::Warn => "WARN",
24 Level::Info => "INFO",
25 Level::Debug => "DEBUG",
26 Level::Trace => "TRACE",
27 }
28 }
29}
30
31pub struct Metadata {
32 pub level: Level,
33 pub target: &'static str,
34}
35
36pub struct Event<'a> {
37 pub meta: &'static Metadata,
38 pub time_ns: u64,
39 pub args: &'a core::fmt::Arguments<'a>,
40}
41
42#[derive(Clone, Copy)]
43pub struct SpanRecord {
44 pub name: &'static str,
45 pub start_ns: u64,
46 pub end_ns: u64,
47 pub depth: u8,
48}
49
50impl SpanRecord {
51 pub fn elapsed_ns(&self) -> u64 {
52 self.end_ns.saturating_sub(self.start_ns)
53 }
54}
55
56pub trait Sink: Send + Sync {
57 fn emit(&self, event: &Event<'_>);
58 fn emit_span(&self, _span: &SpanRecord) {}
59 fn enabled(&self, _level: Level, _target: &'static str) -> bool {
60 true
61 }
62 fn flush(&self) {}
63}
64
65static MAX_LEVEL: AtomicU8 = AtomicU8::new(Level::Info as u8);
66
67pub fn set_max_level(level: Level) {
68 MAX_LEVEL.store(level as u8, Ordering::Relaxed);
69}
70
71pub fn max_level() -> Level {
72 match MAX_LEVEL.load(Ordering::Relaxed) {
73 1 => Level::Error,
74 2 => Level::Warn,
75 3 => Level::Info,
76 4 => Level::Debug,
77 _ => Level::Trace,
78 }
79}
80
81pub const STATIC_MAX_LEVEL: u8 = static_max_level();
82
83const fn static_max_level() -> u8 {
84 if cfg!(feature = "log-max-level-off") {
85 0
86 } else if cfg!(feature = "log-max-level-error") {
87 Level::Error as u8
88 } else if cfg!(feature = "log-max-level-warn") {
89 Level::Warn as u8
90 } else if cfg!(feature = "log-max-level-info") {
91 Level::Info as u8
92 } else if cfg!(feature = "log-max-level-debug") {
93 Level::Debug as u8
94 } else if cfg!(feature = "log-max-level-trace") {
95 Level::Trace as u8
96 } else {
97 u8::MAX
98 }
99}
100
101#[inline]
102pub fn level_enabled(level: Level, _target: &'static str) -> bool {
103 (level as u8) <= MAX_LEVEL.load(Ordering::Relaxed)
104}
105
106#[cfg(feature = "std")]
107mod registry {
108 use super::*;
109 use std::sync::{OnceLock, RwLock};
110
111 static REGISTRY: OnceLock<RwLock<Vec<Box<dyn Sink>>>> = OnceLock::new();
112
113 fn slots() -> &'static RwLock<Vec<Box<dyn Sink>>> {
114 REGISTRY.get_or_init(|| RwLock::new(Vec::new()))
115 }
116
117 pub fn install(sink: Box<dyn Sink>) {
118 slots().write().expect("log registry poisoned").push(sink);
119 }
120
121 pub fn clear() {
122 slots().write().expect("log registry poisoned").clear();
123 }
124
125 pub fn dispatch_to_all(event: &Event<'_>) {
126 let guard = slots().read().expect("log registry poisoned");
127 for sink in guard.iter() {
128 if sink.enabled(event.meta.level, event.meta.target) {
129 sink.emit(event);
130 }
131 }
132 }
133
134 pub fn dispatch_span_to_all(span: &SpanRecord) {
135 let guard = slots().read().expect("log registry poisoned");
136 for sink in guard.iter() {
137 sink.emit_span(span);
138 }
139 }
140}
141
142#[cfg(not(feature = "std"))]
143mod registry {
144 use super::*;
145
146 struct State {
147 sinks: Vec<Box<dyn Sink>>,
148 }
149
150 static mut STATE: State = State { sinks: Vec::new() };
151
152 fn with_state<R>(f: impl FnOnce(&mut State) -> R) -> R {
153 critical_section::with(|_| {
154 #[allow(static_mut_refs)]
155 unsafe {
156 f(&mut STATE)
157 }
158 })
159 }
160
161 pub fn install(sink: Box<dyn Sink>) {
162 with_state(|s| s.sinks.push(sink));
163 }
164
165 pub fn clear() {
166 with_state(|s| s.sinks.clear());
167 }
168
169 pub fn dispatch_to_all(event: &Event<'_>) {
170 with_state(|s| {
171 for sink in s.sinks.iter() {
172 if sink.enabled(event.meta.level, event.meta.target) {
173 sink.emit(event);
174 }
175 }
176 });
177 }
178
179 pub fn dispatch_span_to_all(span: &SpanRecord) {
180 with_state(|s| {
181 for sink in s.sinks.iter() {
182 sink.emit_span(span);
183 }
184 });
185 }
186}
187
188pub fn install_sink(sink: Box<dyn Sink>) {
189 registry::install(sink);
190}
191
192pub fn clear_sinks() {
193 registry::clear();
194}
195
196pub fn dispatch(meta: &'static Metadata, args: core::fmt::Arguments<'_>) {
197 let event = Event {
198 meta,
199 time_ns: clock_now_ns(),
200 args: &args,
201 };
202 registry::dispatch_to_all(&event);
203}
204
205pub fn dispatch_span(span: &SpanRecord) {
206 registry::dispatch_span_to_all(span);
207}
208
209fn clock_now_ns() -> u64 {
210 crate::core::time::clock_now_ns()
211}
212
213#[macro_export]
214macro_rules! __mirui_log {
215 (target: $target:expr, $lvl:expr, $($arg:tt)+) => {{
216 static META: $crate::core::log::Metadata = $crate::core::log::Metadata {
217 level: $lvl,
218 target: $target,
219 };
220 if (META.level as u8) <= $crate::core::log::STATIC_MAX_LEVEL
221 && $crate::core::log::level_enabled(META.level, META.target)
222 {
223 $crate::core::log::dispatch(&META, ::core::format_args!($($arg)+));
224 }
225 }};
226 ($lvl:expr, $($arg:tt)+) => {{
227 static META: $crate::core::log::Metadata = $crate::core::log::Metadata {
228 level: $lvl,
229 target: ::core::module_path!(),
230 };
231 if (META.level as u8) <= $crate::core::log::STATIC_MAX_LEVEL
232 && $crate::core::log::level_enabled(META.level, META.target)
233 {
234 $crate::core::log::dispatch(&META, ::core::format_args!($($arg)+));
235 }
236 }};
237}
238
239#[macro_export]
240macro_rules! error {
241 (target: $target:expr, $($arg:tt)+) => (
242 $crate::__mirui_log!(target: $target, $crate::core::log::Level::Error, $($arg)+)
243 );
244 ($($arg:tt)+) => (
245 $crate::__mirui_log!($crate::core::log::Level::Error, $($arg)+)
246 );
247}
248
249#[macro_export]
250macro_rules! warn {
251 (target: $target:expr, $($arg:tt)+) => (
252 $crate::__mirui_log!(target: $target, $crate::core::log::Level::Warn, $($arg)+)
253 );
254 ($($arg:tt)+) => (
255 $crate::__mirui_log!($crate::core::log::Level::Warn, $($arg)+)
256 );
257}
258
259#[macro_export]
260macro_rules! info {
261 (target: $target:expr, $($arg:tt)+) => (
262 $crate::__mirui_log!(target: $target, $crate::core::log::Level::Info, $($arg)+)
263 );
264 ($($arg:tt)+) => (
265 $crate::__mirui_log!($crate::core::log::Level::Info, $($arg)+)
266 );
267}
268
269#[macro_export]
270macro_rules! debug {
271 (target: $target:expr, $($arg:tt)+) => (
272 $crate::__mirui_log!(target: $target, $crate::core::log::Level::Debug, $($arg)+)
273 );
274 ($($arg:tt)+) => (
275 $crate::__mirui_log!($crate::core::log::Level::Debug, $($arg)+)
276 );
277}
278
279#[macro_export]
280macro_rules! trace {
281 (target: $target:expr, $($arg:tt)+) => (
282 $crate::__mirui_log!(target: $target, $crate::core::log::Level::Trace, $($arg)+)
283 );
284 ($($arg:tt)+) => (
285 $crate::__mirui_log!($crate::core::log::Level::Trace, $($arg)+)
286 );
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use alloc::string::{String, ToString};
293 use alloc::sync::Arc;
294 use core::fmt::Write;
295 use std::sync::Mutex;
296
297 struct CaptureSink {
298 rows: Arc<Mutex<Vec<String>>>,
299 }
300
301 impl Sink for CaptureSink {
302 fn emit(&self, event: &Event<'_>) {
303 let mut msg = String::new();
304 write!(&mut msg, "{}", event.args).unwrap();
305 self.rows.lock().unwrap().push(alloc::format!(
306 "{}:{}:{}",
307 event.meta.level.as_str(),
308 event.meta.target,
309 msg
310 ));
311 }
312 }
313
314 fn install_capture() -> Arc<Mutex<Vec<String>>> {
315 clear_sinks();
316 let rows: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
317 install_sink(Box::new(CaptureSink { rows: rows.clone() }));
318 rows
319 }
320
321 #[test]
322 fn static_max_level_default_is_open() {
323 assert_eq!(STATIC_MAX_LEVEL, u8::MAX);
324 }
325
326 #[test]
327 fn level_ordering() {
328 assert!(Level::Error < Level::Warn);
329 assert!(Level::Warn < Level::Info);
330 assert!(Level::Info < Level::Debug);
331 assert!(Level::Debug < Level::Trace);
332 }
333
334 #[test]
335 fn info_reaches_sink() {
336 let _lock = TEST_MUTEX.lock().unwrap();
337 set_max_level(Level::Info);
338 let rows = install_capture();
339 crate::info!("hello {}", 42);
340 let captured = rows.lock().unwrap().clone();
341 assert_eq!(captured.len(), 1);
342 assert!(captured[0].starts_with("INFO:"));
343 assert!(captured[0].ends_with(":hello 42"));
344 }
345
346 #[test]
347 fn debug_gated_by_max_level() {
348 let _lock = TEST_MUTEX.lock().unwrap();
349 set_max_level(Level::Info);
350 let rows = install_capture();
351 crate::debug!("shhh {}", 7);
352 assert!(rows.lock().unwrap().is_empty());
353 set_max_level(Level::Debug);
354 crate::debug!("now {}", 8);
355 assert_eq!(rows.lock().unwrap().len(), 1);
356 }
357
358 #[test]
359 fn filter_miss_short_circuits_args() {
360 let _lock = TEST_MUTEX.lock().unwrap();
361 set_max_level(Level::Error);
362 install_capture();
363 let mut side_effect = 0;
364 crate::info!("{}", {
365 side_effect += 1;
366 "boom"
367 });
368 assert_eq!(side_effect, 0, "format_args should not evaluate when gated");
369 }
370
371 #[test]
372 fn debug_format_works() {
373 let _lock = TEST_MUTEX.lock().unwrap();
374 set_max_level(Level::Info);
375 let rows = install_capture();
376 let v = alloc::vec![1u32, 2, 3];
377 crate::info!("{:?}", v);
378 let captured = rows.lock().unwrap().clone();
379 assert!(captured[0].ends_with(":[1, 2, 3]"), "got {:?}", captured);
380 }
381
382 #[test]
383 fn custom_target() {
384 let _lock = TEST_MUTEX.lock().unwrap();
385 set_max_level(Level::Info);
386 let rows = install_capture();
387 crate::warn!(target: "mirui::custom", "x");
388 let captured = rows.lock().unwrap().clone();
389 assert!(
390 captured[0].starts_with("WARN:mirui::custom:"),
391 "got {:?}",
392 captured
393 );
394 }
395
396 #[test]
397 fn dispatch_fans_out_to_all_sinks() {
398 let _lock = TEST_MUTEX.lock().unwrap();
399 set_max_level(Level::Info);
400 clear_sinks();
401 let a: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
402 let b: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
403 install_sink(Box::new(CaptureSink { rows: a.clone() }));
404 install_sink(Box::new(CaptureSink { rows: b.clone() }));
405 crate::info!("fan-out {}", 1);
406 assert_eq!(a.lock().unwrap().len(), 1);
407 assert_eq!(b.lock().unwrap().len(), 1);
408 }
409
410 #[test]
411 fn span_dispatch_reaches_sinks() {
412 let _lock = TEST_MUTEX.lock().unwrap();
413 set_max_level(Level::Info);
414
415 struct SpanCapture(Arc<Mutex<Vec<(&'static str, u64)>>>);
416 impl Sink for SpanCapture {
417 fn emit(&self, _: &Event<'_>) {}
418 fn emit_span(&self, span: &SpanRecord) {
419 self.0.lock().unwrap().push((span.name, span.elapsed_ns()));
420 }
421 }
422
423 clear_sinks();
424 let rows: Arc<Mutex<Vec<(&'static str, u64)>>> = Arc::new(Mutex::new(Vec::new()));
425 install_sink(Box::new(SpanCapture(rows.clone())));
426
427 crate::core::perf::set_enabled(true);
428 {
429 let _g = crate::core::perf::enter("test.span");
430 }
431 crate::core::perf::set_enabled(false);
432 let _ = crate::core::perf::drain_events();
433
434 let captured = rows.lock().unwrap().clone();
435 assert_eq!(captured.len(), 1);
436 assert_eq!(captured[0].0, "test.span");
437 }
438
439 #[test]
440 fn sink_enabled_gate_skips_emit() {
441 let _lock = TEST_MUTEX.lock().unwrap();
442 set_max_level(Level::Trace);
443
444 struct MinInfo(Arc<Mutex<Vec<String>>>);
445 impl Sink for MinInfo {
446 fn emit(&self, event: &Event<'_>) {
447 self.0
448 .lock()
449 .unwrap()
450 .push(event.meta.level.as_str().to_string());
451 }
452 fn enabled(&self, level: Level, _target: &'static str) -> bool {
453 level <= Level::Info
454 }
455 }
456
457 clear_sinks();
458 let rows: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
459 install_sink(Box::new(MinInfo(rows.clone())));
460 crate::info!("keep");
461 crate::debug!("drop");
462 let captured = rows.lock().unwrap().clone();
463 assert_eq!(captured, alloc::vec!["INFO".to_string()]);
464 }
465
466 static TEST_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
467}