1#[allow(dead_code)]
9#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
10pub enum LogLevel {
11 Trace = 0,
12 Debug = 1,
13 Info = 2,
14 Warn = 3,
15 Error = 4,
16}
17
18impl LogLevel {
19 fn as_str(self) -> &'static str {
20 match self {
21 LogLevel::Trace => "TRACE",
22 LogLevel::Debug => "DEBUG",
23 LogLevel::Info => "INFO",
24 LogLevel::Warn => "WARN",
25 LogLevel::Error => "ERROR",
26 }
27 }
28}
29
30#[allow(dead_code)]
32#[derive(Clone, Debug)]
33pub struct LogEntry {
34 pub id: u64,
36 pub level: LogLevel,
38 pub category: String,
40 pub message: String,
42}
43
44#[allow(dead_code)]
46pub struct Logger {
47 pub entries: Vec<LogEntry>,
49 pub min_level: LogLevel,
51 next_id: u64,
53}
54
55#[allow(dead_code)]
61pub fn new_logger(min_level: LogLevel) -> Logger {
62 Logger {
63 entries: Vec::new(),
64 min_level,
65 next_id: 1,
66 }
67}
68
69#[allow(dead_code)]
76pub fn log_message(logger: &mut Logger, level: LogLevel, category: &str, message: &str) {
77 if level < logger.min_level {
78 return;
79 }
80 let entry = LogEntry {
81 id: logger.next_id,
82 level,
83 category: category.to_string(),
84 message: message.to_string(),
85 };
86 logger.next_id += 1;
87 logger.entries.push(entry);
88}
89
90#[allow(dead_code)]
92pub fn log_trace(logger: &mut Logger, category: &str, message: &str) {
93 log_message(logger, LogLevel::Trace, category, message);
94}
95
96#[allow(dead_code)]
98pub fn log_debug(logger: &mut Logger, category: &str, message: &str) {
99 log_message(logger, LogLevel::Debug, category, message);
100}
101
102#[allow(dead_code)]
104pub fn log_info(logger: &mut Logger, category: &str, message: &str) {
105 log_message(logger, LogLevel::Info, category, message);
106}
107
108#[allow(dead_code)]
110pub fn log_warn(logger: &mut Logger, category: &str, message: &str) {
111 log_message(logger, LogLevel::Warn, category, message);
112}
113
114#[allow(dead_code)]
116pub fn log_error(logger: &mut Logger, category: &str, message: &str) {
117 log_message(logger, LogLevel::Error, category, message);
118}
119
120#[allow(dead_code)]
126pub fn set_min_level(logger: &mut Logger, level: LogLevel) {
127 logger.min_level = level;
128}
129
130#[allow(dead_code)]
136pub fn entry_count(logger: &Logger) -> usize {
137 logger.entries.len()
138}
139
140#[allow(dead_code)]
142pub fn entries_by_level(logger: &Logger, level: LogLevel) -> Vec<&LogEntry> {
143 logger.entries.iter().filter(|e| e.level == level).collect()
144}
145
146#[allow(dead_code)]
148pub fn clear_log(logger: &mut Logger) {
149 logger.entries.clear();
150}
151
152#[allow(dead_code)]
154pub fn logger_to_json(logger: &Logger) -> String {
155 let mut buf = String::from("[");
156 for (i, entry) in logger.entries.iter().enumerate() {
157 if i > 0 {
158 buf.push(',');
159 }
160 buf.push_str(&format!(
161 r#"{{"id":{},"level":"{}","category":"{}","message":"{}"}}"#,
162 entry.id,
163 entry.level.as_str(),
164 entry.category,
165 entry.message
166 ));
167 }
168 buf.push(']');
169 buf
170}
171
172#[allow(dead_code)]
174pub fn filter_by_category<'a>(logger: &'a Logger, category: &str) -> Vec<&'a LogEntry> {
175 logger
176 .entries
177 .iter()
178 .filter(|e| e.category == category)
179 .collect()
180}
181
182#[allow(dead_code)]
184pub fn last_n_entries(logger: &Logger, n: usize) -> Vec<&LogEntry> {
185 let len = logger.entries.len();
186 let start = len.saturating_sub(n);
187 logger.entries[start..].iter().collect()
188}
189
190#[allow(dead_code)]
192pub fn has_errors(logger: &Logger) -> bool {
193 logger.entries.iter().any(|e| e.level == LogLevel::Error)
194}
195
196#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_new_logger() {
206 let l = new_logger(LogLevel::Info);
207 assert_eq!(entry_count(&l), 0);
208 assert_eq!(l.min_level, LogLevel::Info);
209 }
210
211 #[test]
212 fn test_log_message_above_min() {
213 let mut l = new_logger(LogLevel::Debug);
214 log_message(&mut l, LogLevel::Info, "cat", "hello");
215 assert_eq!(entry_count(&l), 1);
216 }
217
218 #[test]
219 fn test_log_message_below_min_ignored() {
220 let mut l = new_logger(LogLevel::Warn);
221 log_message(&mut l, LogLevel::Debug, "cat", "dropped");
222 assert_eq!(entry_count(&l), 0);
223 }
224
225 #[test]
226 fn test_log_trace() {
227 let mut l = new_logger(LogLevel::Trace);
228 log_trace(&mut l, "test", "trace msg");
229 assert_eq!(entry_count(&l), 1);
230 assert_eq!(l.entries[0].level, LogLevel::Trace);
231 }
232
233 #[test]
234 fn test_log_debug() {
235 let mut l = new_logger(LogLevel::Trace);
236 log_debug(&mut l, "test", "debug msg");
237 assert_eq!(l.entries[0].level, LogLevel::Debug);
238 }
239
240 #[test]
241 fn test_log_info() {
242 let mut l = new_logger(LogLevel::Trace);
243 log_info(&mut l, "test", "info msg");
244 assert_eq!(l.entries[0].level, LogLevel::Info);
245 }
246
247 #[test]
248 fn test_log_warn() {
249 let mut l = new_logger(LogLevel::Trace);
250 log_warn(&mut l, "test", "warn msg");
251 assert_eq!(l.entries[0].level, LogLevel::Warn);
252 }
253
254 #[test]
255 fn test_log_error() {
256 let mut l = new_logger(LogLevel::Trace);
257 log_error(&mut l, "test", "error msg");
258 assert_eq!(l.entries[0].level, LogLevel::Error);
259 }
260
261 #[test]
262 fn test_set_min_level() {
263 let mut l = new_logger(LogLevel::Trace);
264 log_trace(&mut l, "a", "ok");
265 assert_eq!(entry_count(&l), 1);
266 set_min_level(&mut l, LogLevel::Error);
267 log_trace(&mut l, "a", "dropped");
268 assert_eq!(entry_count(&l), 1);
269 }
270
271 #[test]
272 fn test_entries_by_level() {
273 let mut l = new_logger(LogLevel::Trace);
274 log_info(&mut l, "a", "i1");
275 log_warn(&mut l, "a", "w1");
276 log_info(&mut l, "a", "i2");
277 assert_eq!(entries_by_level(&l, LogLevel::Info).len(), 2);
278 assert_eq!(entries_by_level(&l, LogLevel::Warn).len(), 1);
279 }
280
281 #[test]
282 fn test_clear_log() {
283 let mut l = new_logger(LogLevel::Trace);
284 log_info(&mut l, "a", "msg");
285 clear_log(&mut l);
286 assert_eq!(entry_count(&l), 0);
287 }
288
289 #[test]
290 fn test_logger_to_json() {
291 let mut l = new_logger(LogLevel::Trace);
292 log_info(&mut l, "render", "frame done");
293 let json = logger_to_json(&l);
294 assert!(json.contains("render"));
295 assert!(json.contains("frame done"));
296 assert!(json.starts_with('['));
297 assert!(json.ends_with(']'));
298 }
299
300 #[test]
301 fn test_filter_by_category() {
302 let mut l = new_logger(LogLevel::Trace);
303 log_info(&mut l, "render", "r1");
304 log_info(&mut l, "physics", "p1");
305 log_info(&mut l, "render", "r2");
306 assert_eq!(filter_by_category(&l, "render").len(), 2);
307 assert_eq!(filter_by_category(&l, "physics").len(), 1);
308 assert_eq!(filter_by_category(&l, "audio").len(), 0);
309 }
310
311 #[test]
312 fn test_last_n_entries() {
313 let mut l = new_logger(LogLevel::Trace);
314 for i in 0..10 {
315 log_info(&mut l, "t", &format!("msg{i}"));
316 }
317 let last3 = last_n_entries(&l, 3);
318 assert_eq!(last3.len(), 3);
319 assert!(last3[0].message.contains("msg7"));
320 }
321
322 #[test]
323 fn test_last_n_entries_more_than_available() {
324 let mut l = new_logger(LogLevel::Trace);
325 log_info(&mut l, "t", "only one");
326 let result = last_n_entries(&l, 100);
327 assert_eq!(result.len(), 1);
328 }
329
330 #[test]
331 fn test_has_errors_false() {
332 let mut l = new_logger(LogLevel::Trace);
333 log_info(&mut l, "a", "fine");
334 log_warn(&mut l, "a", "warning");
335 assert!(!has_errors(&l));
336 }
337
338 #[test]
339 fn test_has_errors_true() {
340 let mut l = new_logger(LogLevel::Trace);
341 log_error(&mut l, "a", "bad");
342 assert!(has_errors(&l));
343 }
344}