recorder.rs
1 /// Event recorder with thread-safe append-only log 2 use crate::BotEvent; 3 use parking_lot::RwLock; 4 use std::sync::Arc; 5 6 /// Thread-safe event recorder 7 #[derive(Clone)] 8 pub struct EventRecorder { 9 events: Arc<RwLock<Vec<BotEvent>>>, 10 max_events: usize, 11 } 12 13 impl EventRecorder { 14 /// Create a new event recorder 15 pub fn new() -> Self { 16 Self::with_capacity(100_000) 17 } 18 19 /// Create a recorder with a maximum capacity 20 pub fn with_capacity(max_events: usize) -> Self { 21 Self { 22 events: Arc::new(RwLock::new(Vec::new())), 23 max_events, 24 } 25 } 26 27 /// Record a single event 28 pub fn record(&self, event: BotEvent) { 29 let mut events = self.events.write(); 30 31 // If at capacity, remove oldest events (sliding window) 32 if events.len() >= self.max_events { 33 let remove_count = events.len() - self.max_events + 1; 34 events.drain(0..remove_count); 35 } 36 37 events.push(event); 38 } 39 40 /// Record multiple events 41 pub fn record_batch(&self, batch: Vec<BotEvent>) { 42 let mut events = self.events.write(); 43 44 for event in batch { 45 if events.len() >= self.max_events { 46 events.remove(0); 47 } 48 events.push(event); 49 } 50 } 51 52 /// Get all events 53 pub fn get_all(&self) -> Vec<BotEvent> { 54 self.events.read().clone() 55 } 56 57 /// Get events since a timestamp 58 pub fn get_since(&self, timestamp_ms: i64) -> Vec<BotEvent> { 59 self.events 60 .read() 61 .iter() 62 .filter(|e| e.timestamp_ms() >= timestamp_ms) 63 .cloned() 64 .collect() 65 } 66 67 /// Get events for a specific bot 68 pub fn get_for_bot(&self, bot_id: &str) -> Vec<BotEvent> { 69 self.events 70 .read() 71 .iter() 72 .filter(|e| e.bot_id() == Some(bot_id)) 73 .cloned() 74 .collect() 75 } 76 77 /// Get total event count 78 pub fn count(&self) -> usize { 79 self.events.read().len() 80 } 81 82 /// Get error events 83 pub fn get_errors(&self) -> Vec<BotEvent> { 84 self.events 85 .read() 86 .iter() 87 .filter(|e| e.is_error()) 88 .cloned() 89 .collect() 90 } 91 92 /// Clear all events 93 pub fn clear(&self) { 94 self.events.write().clear(); 95 } 96 } 97 98 impl Default for EventRecorder { 99 fn default() -> Self { 100 Self::new() 101 } 102 } 103 104 #[cfg(test)] 105 mod tests { 106 use super::*; 107 108 #[test] 109 fn test_record_event() { 110 let recorder = EventRecorder::new(); 111 112 recorder.record(BotEvent::BotStarted { 113 bot_id: "bot-1".to_string(), 114 role: "trader".to_string(), 115 timestamp_ms: 1000, 116 }); 117 118 assert_eq!(recorder.count(), 1); 119 } 120 121 #[test] 122 fn test_capacity_limit() { 123 let recorder = EventRecorder::with_capacity(10); 124 125 // Record 15 events 126 for i in 0..15 { 127 recorder.record(BotEvent::BotStarted { 128 bot_id: format!("bot-{}", i), 129 role: "trader".to_string(), 130 timestamp_ms: i as i64, 131 }); 132 } 133 134 // Should only keep 10 135 assert_eq!(recorder.count(), 10); 136 137 // Oldest events should be removed 138 let events = recorder.get_all(); 139 assert_eq!(events[0].timestamp_ms(), 5); 140 } 141 142 #[test] 143 fn test_get_since() { 144 let recorder = EventRecorder::new(); 145 146 recorder.record(BotEvent::BotStarted { 147 bot_id: "bot-1".to_string(), 148 role: "trader".to_string(), 149 timestamp_ms: 1000, 150 }); 151 152 recorder.record(BotEvent::BotStarted { 153 bot_id: "bot-2".to_string(), 154 role: "trader".to_string(), 155 timestamp_ms: 2000, 156 }); 157 158 let recent = recorder.get_since(1500); 159 assert_eq!(recent.len(), 1); 160 } 161 162 #[test] 163 fn test_get_for_bot() { 164 let recorder = EventRecorder::new(); 165 166 recorder.record(BotEvent::BotStarted { 167 bot_id: "bot-1".to_string(), 168 role: "trader".to_string(), 169 timestamp_ms: 1000, 170 }); 171 172 recorder.record(BotEvent::BotStarted { 173 bot_id: "bot-2".to_string(), 174 role: "trader".to_string(), 175 timestamp_ms: 2000, 176 }); 177 178 let bot1_events = recorder.get_for_bot("bot-1"); 179 assert_eq!(bot1_events.len(), 1); 180 } 181 }