1#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub enum LogLevel {
6 Trace,
7 Debug,
8 Info,
9 Warn,
10 Error,
11}
12
13impl LogLevel {
14 fn severity(&self) -> u8 {
15 match self {
16 LogLevel::Trace => 0,
17 LogLevel::Debug => 1,
18 LogLevel::Info => 2,
19 LogLevel::Warn => 3,
20 LogLevel::Error => 4,
21 }
22 }
23
24 fn as_str(&self) -> &'static str {
25 match self {
26 LogLevel::Trace => "TRACE",
27 LogLevel::Debug => "DEBUG",
28 LogLevel::Info => "INFO",
29 LogLevel::Warn => "WARN",
30 LogLevel::Error => "ERROR",
31 }
32 }
33}
34
35#[allow(dead_code)]
36#[derive(Clone)]
37pub struct LogEvent {
38 pub id: u64,
39 pub level: LogLevel,
40 pub category: String,
41 pub message: String,
42 pub timestamp: u64,
43 pub data: Vec<(String, String)>,
44}
45
46#[allow(dead_code)]
47pub struct EventLog {
48 pub events: Vec<LogEvent>,
49 pub max_events: usize,
50 pub tick: u64,
51 pub next_id: u64,
52 pub enabled: bool,
53}
54
55#[allow(dead_code)]
56pub fn new_event_log(max_events: usize) -> EventLog {
57 EventLog {
58 events: Vec::new(),
59 max_events,
60 tick: 0,
61 next_id: 1,
62 enabled: true,
63 }
64}
65
66#[allow(dead_code)]
67pub fn log_event(log: &mut EventLog, level: LogLevel, category: &str, msg: &str) {
68 log_with_data(log, level, category, msg, Vec::new());
69}
70
71#[allow(dead_code)]
72pub fn log_with_data(
73 log: &mut EventLog,
74 level: LogLevel,
75 category: &str,
76 msg: &str,
77 data: Vec<(String, String)>,
78) {
79 if !log.enabled {
80 return;
81 }
82 log.tick += 1;
83 let event = LogEvent {
84 id: log.next_id,
85 level,
86 category: category.to_string(),
87 message: msg.to_string(),
88 timestamp: log.tick,
89 data,
90 };
91 log.next_id += 1;
92 log.events.push(event);
93 trim_log(log);
94}
95
96#[allow(dead_code)]
97pub fn filter_by_level(log: &EventLog, min_level: LogLevel) -> Vec<&LogEvent> {
98 let min_sev = min_level.severity();
99 log.events
100 .iter()
101 .filter(|e| e.level.severity() >= min_sev)
102 .collect()
103}
104
105#[allow(dead_code)]
106pub fn filter_by_category<'a>(log: &'a EventLog, category: &str) -> Vec<&'a LogEvent> {
107 log.events
108 .iter()
109 .filter(|e| e.category == category)
110 .collect()
111}
112
113#[allow(dead_code)]
114pub fn event_count(log: &EventLog) -> usize {
115 log.events.len()
116}
117
118#[allow(dead_code)]
119pub fn clear_log(log: &mut EventLog) {
120 log.events.clear();
121}
122
123#[allow(dead_code)]
124pub fn last_event(log: &EventLog) -> Option<&LogEvent> {
125 log.events.last()
126}
127
128#[allow(dead_code)]
129pub fn events_since(log: &EventLog, tick: u64) -> Vec<&LogEvent> {
130 log.events.iter().filter(|e| e.timestamp > tick).collect()
131}
132
133#[allow(dead_code)]
134pub fn error_count(log: &EventLog) -> usize {
135 log.events
136 .iter()
137 .filter(|e| e.level == LogLevel::Error)
138 .count()
139}
140
141#[allow(dead_code)]
142pub fn warn_count(log: &EventLog) -> usize {
143 log.events
144 .iter()
145 .filter(|e| e.level == LogLevel::Warn)
146 .count()
147}
148
149#[allow(dead_code)]
150pub fn serialize_log_json(log: &EventLog) -> String {
151 let mut parts: Vec<String> = Vec::new();
152 for e in &log.events {
153 let data_parts: Vec<String> = e
154 .data
155 .iter()
156 .map(|(k, v)| format!("{{\"key\":\"{}\",\"val\":\"{}\"}}", k, v))
157 .collect();
158 let data_json = format!("[{}]", data_parts.join(","));
159 parts.push(format!(
160 "{{\"id\":{},\"level\":\"{}\",\"category\":\"{}\",\"message\":\"{}\",\"timestamp\":{},\"data\":{}}}",
161 e.id,
162 e.level.as_str(),
163 e.category,
164 e.message,
165 e.timestamp,
166 data_json
167 ));
168 }
169 format!("[{}]", parts.join(","))
170}
171
172#[allow(dead_code)]
173pub fn trim_log(log: &mut EventLog) {
174 if log.max_events == 0 {
175 return;
176 }
177 while log.events.len() > log.max_events {
178 log.events.remove(0);
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_new_event_log() {
188 let log = new_event_log(100);
189 assert!(log.events.is_empty());
190 assert_eq!(log.max_events, 100);
191 assert!(log.enabled);
192 }
193
194 #[test]
195 fn test_log_event_adds_event() {
196 let mut log = new_event_log(100);
197 log_event(&mut log, LogLevel::Info, "test", "hello");
198 assert_eq!(event_count(&log), 1);
199 }
200
201 #[test]
202 fn test_filter_by_level_info_up() {
203 let mut log = new_event_log(100);
204 log_event(&mut log, LogLevel::Debug, "cat", "debug msg");
205 log_event(&mut log, LogLevel::Info, "cat", "info msg");
206 log_event(&mut log, LogLevel::Error, "cat", "error msg");
207 let filtered = filter_by_level(&log, LogLevel::Info);
208 assert_eq!(filtered.len(), 2);
209 }
210
211 #[test]
212 fn test_filter_by_category() {
213 let mut log = new_event_log(100);
214 log_event(&mut log, LogLevel::Info, "mesh", "msg1");
215 log_event(&mut log, LogLevel::Info, "morph", "msg2");
216 log_event(&mut log, LogLevel::Info, "mesh", "msg3");
217 let filtered = filter_by_category(&log, "mesh");
218 assert_eq!(filtered.len(), 2);
219 }
220
221 #[test]
222 fn test_error_count() {
223 let mut log = new_event_log(100);
224 log_event(&mut log, LogLevel::Info, "c", "m");
225 log_event(&mut log, LogLevel::Error, "c", "e1");
226 log_event(&mut log, LogLevel::Error, "c", "e2");
227 assert_eq!(error_count(&log), 2);
228 }
229
230 #[test]
231 fn test_warn_count() {
232 let mut log = new_event_log(100);
233 log_event(&mut log, LogLevel::Warn, "c", "w1");
234 log_event(&mut log, LogLevel::Info, "c", "i");
235 assert_eq!(warn_count(&log), 1);
236 }
237
238 #[test]
239 fn test_clear_log() {
240 let mut log = new_event_log(100);
241 log_event(&mut log, LogLevel::Info, "c", "m");
242 log_event(&mut log, LogLevel::Info, "c", "m");
243 clear_log(&mut log);
244 assert_eq!(event_count(&log), 0);
245 }
246
247 #[test]
248 fn test_last_event() {
249 let mut log = new_event_log(100);
250 log_event(&mut log, LogLevel::Info, "c", "first");
251 log_event(&mut log, LogLevel::Error, "c", "last");
252 let last = last_event(&log).expect("should succeed");
253 assert_eq!(last.message, "last");
254 }
255
256 #[test]
257 fn test_last_event_empty() {
258 let log = new_event_log(100);
259 assert!(last_event(&log).is_none());
260 }
261
262 #[test]
263 fn test_events_since() {
264 let mut log = new_event_log(100);
265 log_event(&mut log, LogLevel::Info, "c", "m1");
266 let tick_after_first = log.tick;
267 log_event(&mut log, LogLevel::Info, "c", "m2");
268 log_event(&mut log, LogLevel::Info, "c", "m3");
269 let since = events_since(&log, tick_after_first);
270 assert_eq!(since.len(), 2);
271 }
272
273 #[test]
274 fn test_trim_enforces_max() {
275 let mut log = new_event_log(3);
276 for i in 0..6 {
277 log_event(&mut log, LogLevel::Info, "c", &format!("msg{}", i));
278 }
279 assert!(log.events.len() <= 3);
280 }
281
282 #[test]
283 fn test_serialize_non_empty() {
284 let mut log = new_event_log(100);
285 log_event(&mut log, LogLevel::Info, "cat", "hello world");
286 let json = serialize_log_json(&log);
287 assert!(json.contains("hello world"));
288 assert!(json.contains("INFO"));
289 assert!(json.starts_with('['));
290 assert!(json.ends_with(']'));
291 }
292
293 #[test]
294 fn test_log_with_data() {
295 let mut log = new_event_log(100);
296 log_with_data(
297 &mut log,
298 LogLevel::Debug,
299 "sys",
300 "test",
301 vec![("key1".to_string(), "val1".to_string())],
302 );
303 let e = last_event(&log).expect("should succeed");
304 assert_eq!(e.data.len(), 1);
305 assert_eq!(e.data[0].0, "key1");
306 }
307
308 #[test]
309 fn test_ids_increment() {
310 let mut log = new_event_log(100);
311 log_event(&mut log, LogLevel::Info, "c", "m1");
312 log_event(&mut log, LogLevel::Info, "c", "m2");
313 assert!(log.events[1].id > log.events[0].id);
314 }
315
316 #[test]
317 fn test_disabled_log_ignores_events() {
318 let mut log = new_event_log(100);
319 log.enabled = false;
320 log_event(&mut log, LogLevel::Error, "c", "msg");
321 assert_eq!(event_count(&log), 0);
322 }
323}