1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct EventEntry {
11 pub timestamp_ms: u64,
12 pub event_type: String,
13 pub severity: EventSeverity,
14 pub payload: String,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone, PartialEq)]
20pub enum EventSeverity {
21 Debug,
22 Info,
23 Warning,
24 Error,
25}
26
27impl EventSeverity {
28 pub fn as_str(&self) -> &str {
29 match self {
30 EventSeverity::Debug => "DEBUG",
31 EventSeverity::Info => "INFO",
32 EventSeverity::Warning => "WARNING",
33 EventSeverity::Error => "ERROR",
34 }
35 }
36}
37
38#[allow(dead_code)]
40pub struct EventLog {
41 pub entries: Vec<EventEntry>,
42}
43
44impl EventLog {
45 #[allow(dead_code)]
46 pub fn new() -> Self {
47 Self {
48 entries: Vec::new(),
49 }
50 }
51}
52
53impl Default for EventLog {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59#[allow(dead_code)]
61pub fn log_event(
62 log: &mut EventLog,
63 timestamp_ms: u64,
64 event_type: &str,
65 severity: EventSeverity,
66 payload: &str,
67) {
68 log.entries.push(EventEntry {
69 timestamp_ms,
70 event_type: event_type.to_string(),
71 severity,
72 payload: payload.to_string(),
73 });
74}
75
76#[allow(dead_code)]
78pub fn export_event_log_csv(log: &EventLog) -> String {
79 let mut out = String::from("timestamp_ms,event_type,severity,payload\n");
80 for e in &log.entries {
81 out.push_str(&format!(
82 "{},{},{},{}\n",
83 e.timestamp_ms,
84 e.event_type,
85 e.severity.as_str(),
86 e.payload
87 ));
88 }
89 out
90}
91
92#[allow(dead_code)]
94pub fn export_event_log_ndjson(log: &EventLog) -> String {
95 let mut out = String::new();
96 for e in &log.entries {
97 out.push_str(&format!(
98 "{{\"ts\":{},\"type\":\"{}\",\"severity\":\"{}\",\"payload\":\"{}\"}}\n",
99 e.timestamp_ms,
100 e.event_type,
101 e.severity.as_str(),
102 e.payload
103 ));
104 }
105 out
106}
107
108#[allow(dead_code)]
110pub fn count_by_severity(log: &EventLog, severity: &EventSeverity) -> usize {
111 log.entries
112 .iter()
113 .filter(|e| &e.severity == severity)
114 .count()
115}
116
117#[allow(dead_code)]
119pub fn event_count(log: &EventLog) -> usize {
120 log.entries.len()
121}
122
123#[allow(dead_code)]
125pub fn filter_by_type<'a>(log: &'a EventLog, event_type: &str) -> Vec<&'a EventEntry> {
126 log.entries
127 .iter()
128 .filter(|e| e.event_type == event_type)
129 .collect()
130}
131
132#[allow(dead_code)]
134pub fn has_errors(log: &EventLog) -> bool {
135 log.entries
136 .iter()
137 .any(|e| e.severity == EventSeverity::Error)
138}
139
140#[allow(dead_code)]
142pub fn sort_events_by_time(log: &mut EventLog) {
143 log.entries.sort_by_key(|e| e.timestamp_ms);
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn sample_log() -> EventLog {
151 let mut log = EventLog::new();
152 log_event(
153 &mut log,
154 0,
155 "startup",
156 EventSeverity::Info,
157 "system started",
158 );
159 log_event(
160 &mut log,
161 100,
162 "mesh_load",
163 EventSeverity::Info,
164 "mesh loaded OK",
165 );
166 log_event(
167 &mut log,
168 200,
169 "export_fail",
170 EventSeverity::Error,
171 "write error",
172 );
173 log_event(
174 &mut log,
175 300,
176 "debug_dump",
177 EventSeverity::Debug,
178 "vertices=100",
179 );
180 log
181 }
182
183 #[test]
184 fn event_count_correct() {
185 let log = sample_log();
186 assert_eq!(event_count(&log), 4);
187 }
188
189 #[test]
190 fn count_errors_one() {
191 let log = sample_log();
192 assert_eq!(count_by_severity(&log, &EventSeverity::Error), 1);
193 }
194
195 #[test]
196 fn has_errors_true() {
197 let log = sample_log();
198 assert!(has_errors(&log));
199 }
200
201 #[test]
202 fn no_errors_when_clean() {
203 let log = EventLog::new();
204 assert!(!has_errors(&log));
205 }
206
207 #[test]
208 fn csv_header_present() {
209 let log = sample_log();
210 let csv = export_event_log_csv(&log);
211 assert!(csv.starts_with("timestamp_ms,event_type,severity,payload"));
212 }
213
214 #[test]
215 fn ndjson_line_count() {
216 let log = sample_log();
217 let ndjson = export_event_log_ndjson(&log);
218 let lines: Vec<&str> = ndjson.trim().split('\n').collect();
219 assert_eq!(lines.len(), 4);
220 }
221
222 #[test]
223 fn filter_by_type_correct() {
224 let log = sample_log();
225 let entries = filter_by_type(&log, "startup");
226 assert_eq!(entries.len(), 1);
227 }
228
229 #[test]
230 fn sort_events_by_time_ordered() {
231 let mut log = EventLog::new();
232 log_event(&mut log, 300, "c", EventSeverity::Info, "");
233 log_event(&mut log, 100, "a", EventSeverity::Info, "");
234 log_event(&mut log, 200, "b", EventSeverity::Info, "");
235 sort_events_by_time(&mut log);
236 assert_eq!(log.entries[0].timestamp_ms, 100);
237 }
238
239 #[test]
240 fn severity_as_str_correct() {
241 assert_eq!(EventSeverity::Error.as_str(), "ERROR");
242 assert_eq!(EventSeverity::Warning.as_str(), "WARNING");
243 }
244
245 #[test]
246 fn count_info_two() {
247 let log = sample_log();
248 assert_eq!(count_by_severity(&log, &EventSeverity::Info), 2);
249 }
250}