1use std::collections::{HashMap, VecDeque};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum EventType {
13 BlockAdded,
15 BlockDeleted,
17 BlockAccessed,
19 CompactionStarted,
21 CompactionCompleted,
23 QuotaExceeded,
25 Error,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum EventSeverity {
32 Info,
34 Warning,
36 Error,
38 Critical,
40}
41
42#[derive(Debug, Clone)]
44pub struct StorageEvent {
45 pub id: u64,
47 pub event_type: EventType,
49 pub severity: EventSeverity,
51 pub message: String,
53 pub block_cid: Option<String>,
55 pub tick: u64,
57 pub metadata: HashMap<String, String>,
59}
60
61#[derive(Debug, Clone)]
63pub struct EventLogStats {
64 pub total_events: usize,
66 pub events_by_severity: HashMap<String, u64>,
68 pub oldest_tick: Option<u64>,
70 pub newest_tick: Option<u64>,
72}
73
74pub struct StorageEventLog {
79 events: VecDeque<StorageEvent>,
80 max_events: usize,
81 next_id: u64,
82 current_tick: u64,
83 counts_by_type: HashMap<EventType, u64>,
84}
85
86impl StorageEventLog {
87 pub fn new(max_events: usize) -> Self {
89 Self {
90 events: VecDeque::new(),
91 max_events,
92 next_id: 0,
93 current_tick: 0,
94 counts_by_type: HashMap::new(),
95 }
96 }
97
98 pub fn log(
102 &mut self,
103 event_type: EventType,
104 severity: EventSeverity,
105 message: &str,
106 block_cid: Option<&str>,
107 metadata: HashMap<String, String>,
108 ) -> u64 {
109 let id = self.next_id;
110 self.next_id = self.next_id.saturating_add(1);
111
112 let event = StorageEvent {
113 id,
114 event_type,
115 severity,
116 message: message.to_string(),
117 block_cid: block_cid.map(|s| s.to_string()),
118 tick: self.current_tick,
119 metadata,
120 };
121
122 if self.events.len() >= self.max_events {
124 self.events.pop_front();
125 }
126
127 *self.counts_by_type.entry(event_type).or_insert(0) += 1;
128 self.events.push_back(event);
129
130 id
131 }
132
133 pub fn get_event(&self, id: u64) -> Option<&StorageEvent> {
135 self.events.iter().find(|e| e.id == id)
136 }
137
138 pub fn events_by_type(&self, event_type: EventType) -> Vec<&StorageEvent> {
140 self.events
141 .iter()
142 .filter(|e| e.event_type == event_type)
143 .collect()
144 }
145
146 pub fn events_by_severity(&self, severity: EventSeverity) -> Vec<&StorageEvent> {
148 self.events
149 .iter()
150 .filter(|e| e.severity == severity)
151 .collect()
152 }
153
154 pub fn events_since(&self, tick: u64) -> Vec<&StorageEvent> {
156 self.events.iter().filter(|e| e.tick > tick).collect()
157 }
158
159 pub fn count_by_type(&self, event_type: EventType) -> u64 {
162 self.counts_by_type.get(&event_type).copied().unwrap_or(0)
163 }
164
165 pub fn recent(&self, n: usize) -> Vec<&StorageEvent> {
167 let len = self.events.len();
168 let skip = len.saturating_sub(n);
169 self.events.iter().skip(skip).collect()
170 }
171
172 pub fn tick(&mut self) {
174 self.current_tick = self.current_tick.saturating_add(1);
175 }
176
177 pub fn clear(&mut self) {
181 self.events.clear();
182 }
183
184 pub fn event_count(&self) -> usize {
186 self.events.len()
187 }
188
189 pub fn stats(&self) -> EventLogStats {
191 let mut events_by_severity: HashMap<String, u64> = HashMap::new();
192 for event in &self.events {
193 let key = format!("{:?}", event.severity);
194 *events_by_severity.entry(key).or_insert(0) += 1;
195 }
196
197 let oldest_tick = self.events.front().map(|e| e.tick);
198 let newest_tick = self.events.back().map(|e| e.tick);
199
200 EventLogStats {
201 total_events: self.events.len(),
202 events_by_severity,
203 oldest_tick,
204 newest_tick,
205 }
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 fn empty_meta() -> HashMap<String, String> {
214 HashMap::new()
215 }
216
217 #[test]
219 fn test_log_returns_id() {
220 let mut log = StorageEventLog::new(100);
221 let id = log.log(
222 EventType::BlockAdded,
223 EventSeverity::Info,
224 "added",
225 None,
226 empty_meta(),
227 );
228 assert_eq!(id, 0);
229 }
230
231 #[test]
233 fn test_sequential_ids() {
234 let mut log = StorageEventLog::new(100);
235 let a = log.log(
236 EventType::BlockAdded,
237 EventSeverity::Info,
238 "a",
239 None,
240 empty_meta(),
241 );
242 let b = log.log(
243 EventType::BlockDeleted,
244 EventSeverity::Info,
245 "b",
246 None,
247 empty_meta(),
248 );
249 assert_eq!(a, 0);
250 assert_eq!(b, 1);
251 }
252
253 #[test]
255 fn test_get_event() {
256 let mut log = StorageEventLog::new(100);
257 let id = log.log(
258 EventType::BlockAccessed,
259 EventSeverity::Warning,
260 "access",
261 Some("Qm123"),
262 empty_meta(),
263 );
264 let evt = log.get_event(id).expect("event should exist");
265 assert_eq!(evt.event_type, EventType::BlockAccessed);
266 assert_eq!(evt.severity, EventSeverity::Warning);
267 assert_eq!(evt.message, "access");
268 assert_eq!(evt.block_cid.as_deref(), Some("Qm123"));
269 }
270
271 #[test]
273 fn test_get_event_missing() {
274 let log = StorageEventLog::new(100);
275 assert!(log.get_event(42).is_none());
276 }
277
278 #[test]
280 fn test_events_by_type() {
281 let mut log = StorageEventLog::new(100);
282 log.log(
283 EventType::BlockAdded,
284 EventSeverity::Info,
285 "a",
286 None,
287 empty_meta(),
288 );
289 log.log(
290 EventType::BlockDeleted,
291 EventSeverity::Info,
292 "b",
293 None,
294 empty_meta(),
295 );
296 log.log(
297 EventType::BlockAdded,
298 EventSeverity::Info,
299 "c",
300 None,
301 empty_meta(),
302 );
303 let added = log.events_by_type(EventType::BlockAdded);
304 assert_eq!(added.len(), 2);
305 }
306
307 #[test]
309 fn test_events_by_severity() {
310 let mut log = StorageEventLog::new(100);
311 log.log(
312 EventType::Error,
313 EventSeverity::Error,
314 "err1",
315 None,
316 empty_meta(),
317 );
318 log.log(
319 EventType::BlockAdded,
320 EventSeverity::Info,
321 "ok",
322 None,
323 empty_meta(),
324 );
325 log.log(
326 EventType::Error,
327 EventSeverity::Error,
328 "err2",
329 None,
330 empty_meta(),
331 );
332 let errors = log.events_by_severity(EventSeverity::Error);
333 assert_eq!(errors.len(), 2);
334 }
335
336 #[test]
338 fn test_events_since() {
339 let mut log = StorageEventLog::new(100);
340 log.log(
341 EventType::BlockAdded,
342 EventSeverity::Info,
343 "t0",
344 None,
345 empty_meta(),
346 );
347 log.tick();
348 log.log(
349 EventType::BlockAdded,
350 EventSeverity::Info,
351 "t1",
352 None,
353 empty_meta(),
354 );
355 log.tick();
356 log.log(
357 EventType::BlockAdded,
358 EventSeverity::Info,
359 "t2",
360 None,
361 empty_meta(),
362 );
363 let since = log.events_since(0);
364 assert_eq!(since.len(), 2);
365 }
366
367 #[test]
369 fn test_count_by_type() {
370 let mut log = StorageEventLog::new(100);
371 log.log(
372 EventType::BlockAdded,
373 EventSeverity::Info,
374 "a",
375 None,
376 empty_meta(),
377 );
378 log.log(
379 EventType::BlockAdded,
380 EventSeverity::Info,
381 "b",
382 None,
383 empty_meta(),
384 );
385 log.log(
386 EventType::BlockDeleted,
387 EventSeverity::Info,
388 "c",
389 None,
390 empty_meta(),
391 );
392 assert_eq!(log.count_by_type(EventType::BlockAdded), 2);
393 assert_eq!(log.count_by_type(EventType::BlockDeleted), 1);
394 assert_eq!(log.count_by_type(EventType::Error), 0);
395 }
396
397 #[test]
399 fn test_recent() {
400 let mut log = StorageEventLog::new(100);
401 for i in 0..5 {
402 log.log(
403 EventType::BlockAdded,
404 EventSeverity::Info,
405 &format!("e{i}"),
406 None,
407 empty_meta(),
408 );
409 }
410 let r = log.recent(3);
411 assert_eq!(r.len(), 3);
412 assert_eq!(r[0].id, 2);
413 assert_eq!(r[2].id, 4);
414 }
415
416 #[test]
418 fn test_recent_exceeds_count() {
419 let mut log = StorageEventLog::new(100);
420 log.log(
421 EventType::BlockAdded,
422 EventSeverity::Info,
423 "only",
424 None,
425 empty_meta(),
426 );
427 let r = log.recent(10);
428 assert_eq!(r.len(), 1);
429 }
430
431 #[test]
433 fn test_max_events_eviction() {
434 let mut log = StorageEventLog::new(3);
435 for i in 0..5 {
436 log.log(
437 EventType::BlockAdded,
438 EventSeverity::Info,
439 &format!("e{i}"),
440 None,
441 empty_meta(),
442 );
443 }
444 assert_eq!(log.event_count(), 3);
445 assert!(log.get_event(0).is_none());
447 assert!(log.get_event(1).is_none());
448 assert!(log.get_event(2).is_some());
449 }
450
451 #[test]
453 fn test_eviction_preserves_counts() {
454 let mut log = StorageEventLog::new(2);
455 for _ in 0..5 {
456 log.log(
457 EventType::BlockAdded,
458 EventSeverity::Info,
459 "x",
460 None,
461 empty_meta(),
462 );
463 }
464 assert_eq!(log.count_by_type(EventType::BlockAdded), 5);
465 }
466
467 #[test]
469 fn test_clear() {
470 let mut log = StorageEventLog::new(100);
471 log.log(
472 EventType::BlockAdded,
473 EventSeverity::Info,
474 "a",
475 None,
476 empty_meta(),
477 );
478 log.log(
479 EventType::BlockDeleted,
480 EventSeverity::Info,
481 "b",
482 None,
483 empty_meta(),
484 );
485 log.clear();
486 assert_eq!(log.event_count(), 0);
487 }
488
489 #[test]
491 fn test_clear_preserves_counts() {
492 let mut log = StorageEventLog::new(100);
493 log.log(
494 EventType::BlockAdded,
495 EventSeverity::Info,
496 "a",
497 None,
498 empty_meta(),
499 );
500 log.clear();
501 assert_eq!(log.count_by_type(EventType::BlockAdded), 1);
502 }
503
504 #[test]
506 fn test_stats_total() {
507 let mut log = StorageEventLog::new(100);
508 log.log(
509 EventType::BlockAdded,
510 EventSeverity::Info,
511 "a",
512 None,
513 empty_meta(),
514 );
515 log.log(
516 EventType::Error,
517 EventSeverity::Error,
518 "e",
519 None,
520 empty_meta(),
521 );
522 let s = log.stats();
523 assert_eq!(s.total_events, 2);
524 }
525
526 #[test]
528 fn test_stats_severity_counts() {
529 let mut log = StorageEventLog::new(100);
530 log.log(
531 EventType::BlockAdded,
532 EventSeverity::Info,
533 "a",
534 None,
535 empty_meta(),
536 );
537 log.log(
538 EventType::Error,
539 EventSeverity::Error,
540 "e",
541 None,
542 empty_meta(),
543 );
544 log.log(
545 EventType::QuotaExceeded,
546 EventSeverity::Warning,
547 "q",
548 None,
549 empty_meta(),
550 );
551 let s = log.stats();
552 assert_eq!(s.events_by_severity.get("Info").copied(), Some(1));
553 assert_eq!(s.events_by_severity.get("Error").copied(), Some(1));
554 assert_eq!(s.events_by_severity.get("Warning").copied(), Some(1));
555 }
556
557 #[test]
559 fn test_stats_tick_range() {
560 let mut log = StorageEventLog::new(100);
561 log.log(
562 EventType::BlockAdded,
563 EventSeverity::Info,
564 "t0",
565 None,
566 empty_meta(),
567 );
568 log.tick();
569 log.tick();
570 log.log(
571 EventType::BlockAdded,
572 EventSeverity::Info,
573 "t2",
574 None,
575 empty_meta(),
576 );
577 let s = log.stats();
578 assert_eq!(s.oldest_tick, Some(0));
579 assert_eq!(s.newest_tick, Some(2));
580 }
581
582 #[test]
584 fn test_stats_empty() {
585 let log = StorageEventLog::new(100);
586 let s = log.stats();
587 assert_eq!(s.total_events, 0);
588 assert!(s.oldest_tick.is_none());
589 assert!(s.newest_tick.is_none());
590 }
591
592 #[test]
594 fn test_multiple_event_types() {
595 let mut log = StorageEventLog::new(100);
596 log.log(
597 EventType::CompactionStarted,
598 EventSeverity::Info,
599 "start",
600 None,
601 empty_meta(),
602 );
603 log.log(
604 EventType::CompactionCompleted,
605 EventSeverity::Info,
606 "done",
607 None,
608 empty_meta(),
609 );
610 assert_eq!(log.events_by_type(EventType::CompactionStarted).len(), 1);
611 assert_eq!(log.events_by_type(EventType::CompactionCompleted).len(), 1);
612 }
613
614 #[test]
616 fn test_tick_tracking() {
617 let mut log = StorageEventLog::new(100);
618 log.log(
619 EventType::BlockAdded,
620 EventSeverity::Info,
621 "t0",
622 None,
623 empty_meta(),
624 );
625 log.tick();
626 log.tick();
627 log.tick();
628 log.log(
629 EventType::BlockAdded,
630 EventSeverity::Info,
631 "t3",
632 None,
633 empty_meta(),
634 );
635 let evt = log.get_event(1).expect("should exist");
636 assert_eq!(evt.tick, 3);
637 }
638
639 #[test]
641 fn test_metadata_preserved() {
642 let mut log = StorageEventLog::new(100);
643 let mut meta = HashMap::new();
644 meta.insert("size".to_string(), "1024".to_string());
645 meta.insert("codec".to_string(), "dag-pb".to_string());
646 let id = log.log(
647 EventType::BlockAdded,
648 EventSeverity::Info,
649 "m",
650 Some("QmABC"),
651 meta,
652 );
653 let evt = log.get_event(id).expect("should exist");
654 assert_eq!(evt.metadata.get("size").map(|s| s.as_str()), Some("1024"));
655 assert_eq!(
656 evt.metadata.get("codec").map(|s| s.as_str()),
657 Some("dag-pb")
658 );
659 }
660
661 #[test]
663 fn test_event_count() {
664 let mut log = StorageEventLog::new(100);
665 assert_eq!(log.event_count(), 0);
666 log.log(
667 EventType::BlockAdded,
668 EventSeverity::Info,
669 "a",
670 None,
671 empty_meta(),
672 );
673 assert_eq!(log.event_count(), 1);
674 log.log(
675 EventType::BlockDeleted,
676 EventSeverity::Info,
677 "b",
678 None,
679 empty_meta(),
680 );
681 assert_eq!(log.event_count(), 2);
682 }
683
684 #[test]
686 fn test_critical_severity() {
687 let mut log = StorageEventLog::new(100);
688 log.log(
689 EventType::Error,
690 EventSeverity::Critical,
691 "disk full",
692 None,
693 empty_meta(),
694 );
695 let critical = log.events_by_severity(EventSeverity::Critical);
696 assert_eq!(critical.len(), 1);
697 assert_eq!(critical[0].message, "disk full");
698 }
699
700 #[test]
702 fn test_events_since_no_matches() {
703 let mut log = StorageEventLog::new(100);
704 log.log(
705 EventType::BlockAdded,
706 EventSeverity::Info,
707 "old",
708 None,
709 empty_meta(),
710 );
711 let since = log.events_since(0);
712 assert!(since.is_empty());
713 }
714
715 #[test]
717 fn test_quota_exceeded_type() {
718 let mut log = StorageEventLog::new(100);
719 log.log(
720 EventType::QuotaExceeded,
721 EventSeverity::Warning,
722 "over limit",
723 None,
724 empty_meta(),
725 );
726 assert_eq!(log.count_by_type(EventType::QuotaExceeded), 1);
727 let by_type = log.events_by_type(EventType::QuotaExceeded);
728 assert_eq!(by_type.len(), 1);
729 }
730
731 #[test]
733 fn test_block_cid_none() {
734 let mut log = StorageEventLog::new(100);
735 let id = log.log(
736 EventType::CompactionStarted,
737 EventSeverity::Info,
738 "compact",
739 None,
740 empty_meta(),
741 );
742 let evt = log.get_event(id).expect("should exist");
743 assert!(evt.block_cid.is_none());
744 }
745
746 #[test]
748 fn test_max_events_one() {
749 let mut log = StorageEventLog::new(1);
750 log.log(
751 EventType::BlockAdded,
752 EventSeverity::Info,
753 "first",
754 None,
755 empty_meta(),
756 );
757 log.log(
758 EventType::BlockDeleted,
759 EventSeverity::Info,
760 "second",
761 None,
762 empty_meta(),
763 );
764 assert_eq!(log.event_count(), 1);
765 assert!(log.get_event(0).is_none());
766 assert!(log.get_event(1).is_some());
767 }
768
769 #[test]
771 fn test_tick_immutable_on_existing() {
772 let mut log = StorageEventLog::new(100);
773 log.log(
774 EventType::BlockAdded,
775 EventSeverity::Info,
776 "before",
777 None,
778 empty_meta(),
779 );
780 log.tick();
781 let evt = log.get_event(0).expect("should exist");
782 assert_eq!(evt.tick, 0);
783 }
784
785 #[test]
787 fn test_recent_zero() {
788 let mut log = StorageEventLog::new(100);
789 log.log(
790 EventType::BlockAdded,
791 EventSeverity::Info,
792 "a",
793 None,
794 empty_meta(),
795 );
796 assert!(log.recent(0).is_empty());
797 }
798
799 #[test]
801 fn test_combined_filter() {
802 let mut log = StorageEventLog::new(100);
803 log.log(
804 EventType::Error,
805 EventSeverity::Error,
806 "e1",
807 None,
808 empty_meta(),
809 );
810 log.log(
811 EventType::Error,
812 EventSeverity::Critical,
813 "e2",
814 None,
815 empty_meta(),
816 );
817 log.log(
818 EventType::BlockAdded,
819 EventSeverity::Error,
820 "e3",
821 None,
822 empty_meta(),
823 );
824 let errors_of_type: Vec<_> = log
825 .events_by_type(EventType::Error)
826 .into_iter()
827 .filter(|e| e.severity == EventSeverity::Critical)
828 .collect();
829 assert_eq!(errors_of_type.len(), 1);
830 assert_eq!(errors_of_type[0].message, "e2");
831 }
832}