1use std::cell::RefCell;
2use std::collections::VecDeque;
3use std::fmt;
4use std::thread::LocalKey;
5
6#[macro_export]
8macro_rules! declare_log_buffer {
9 (name = $name:ident, capacity = $cap:expr) => {
10 thread_local! {
11 pub static $name: std::cell::RefCell<$crate::LogBuffer> =
12 std::cell::RefCell::new($crate::LogBuffer::with_capacity($cap));
13 }
14 };
15}
16
17pub mod entry_counter {
18 thread_local! {
19 static ENTRY_COUNTER: std::cell::Cell<u64> = Default::default();
20 }
21 pub fn increment() -> u64 {
22 ENTRY_COUNTER.with(|cell| {
23 cell.set(cell.get() + 1);
24 cell.get()
25 })
26 }
27 pub fn set(value: u64) {
28 ENTRY_COUNTER.with(|cell| cell.set(value));
29 }
30 pub fn get() -> u64 {
31 ENTRY_COUNTER.with(|cell| cell.get())
32 }
33}
34
35#[macro_export]
50macro_rules! log {
51 ($sink:expr, $message:expr $(,$args:expr)* $(,)*) => {{
52 use $crate::Sink;
53 let message = std::format!($message $(,$args)*);
54 println!("{}", &message);
56 (&$sink).append($crate::LogEntry {
57 timestamp: $crate::now(),
58 message,
59 file: std::file!(),
60 line: std::line!(),
61 counter: $crate::entry_counter::increment()
62 });
63 }}
64}
65
66pub trait Sink {
67 fn append(&self, entry: LogEntry);
68}
69
70#[derive(Debug, serde::Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
72pub struct LogEntry {
73 pub timestamp: u64,
74 pub counter: u64,
76 pub message: String,
77 pub file: &'static str,
78 pub line: u32,
79}
80
81impl fmt::Display for LogEntry {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(
84 f,
85 "[{}] {}:{} {}",
86 self.timestamp, self.file, self.line, self.message
87 )
88 }
89}
90
91#[derive(Clone)]
92pub struct DevNull;
93
94impl Sink for DevNull {
95 fn append(&self, _: LogEntry) {}
96}
97
98pub struct LogBuffer {
100 max_capacity: usize,
101 entries: VecDeque<LogEntry>,
102}
103
104impl LogBuffer {
105 pub fn with_capacity(max_capacity: usize) -> Self {
107 Self {
108 max_capacity,
109 entries: VecDeque::with_capacity(max_capacity),
110 }
111 }
112
113 pub fn append(&mut self, entry: LogEntry) {
115 if self.entries.len() >= self.max_capacity {
116 self.entries.pop_front();
117 }
118 self.entries.push_back(entry);
119 }
120
121 pub fn iter(&self) -> impl Iterator<Item = &LogEntry> {
123 self.entries.iter()
124 }
125
126 pub fn entries_partition_point<P>(&self, p: P) -> impl Iterator<Item = &LogEntry>
147 where
148 P: Fn(&LogEntry) -> bool,
149 {
150 let head_len = self.entries.partition_point(p);
151 self.iter().skip(head_len)
152 }
153}
154
155pub type GlobalBuffer = LocalKey<RefCell<LogBuffer>>;
156
157impl Sink for &'static GlobalBuffer {
158 fn append(&self, entry: LogEntry) {
159 self.with(|cell| cell.borrow_mut().append(entry))
160 }
161}
162
163mod private {
164 #[cfg(target_arch = "wasm32")]
165 #[link(wasm_import_module = "ic0")]
166 extern "C" {
167 pub fn time() -> u64;
168 }
169
170 #[cfg(target_arch = "wasm32")]
171 pub fn timestamp() -> u64 {
172 unsafe { time() }
173 }
174
175 #[cfg(not(target_arch = "wasm32"))]
176 pub fn timestamp() -> u64 {
177 use std::time::SystemTime;
178
179 match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
180 Ok(d) => d.as_nanos() as u64,
181 Err(_) => panic!("SystemTime before UNIX EPOCH!"),
182 }
183 }
184}
185
186#[doc(hidden)]
189pub fn now() -> u64 {
190 private::timestamp()
191}
192
193pub fn export(buf: &'static GlobalBuffer) -> Vec<LogEntry> {
208 buf.with(|cell| cell.borrow().iter().cloned().collect())
209}