oxihuman_core/
ring_log.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum RingLogLevel {
10 Trace,
11 Debug,
12 Info,
13 Warn,
14 Error,
15}
16
17#[derive(Debug, Clone)]
19pub struct RingLogEntry {
20 pub level: RingLogLevel,
21 pub message: String,
22 pub seq: u64,
23}
24
25pub struct RingLog {
27 buf: Vec<Option<RingLogEntry>>,
28 capacity: usize,
29 head: usize,
30 count: usize,
31 seq: u64,
32 error_count: usize,
33 warn_count: usize,
34}
35
36#[allow(dead_code)]
37impl RingLog {
38 pub fn new(capacity: usize) -> Self {
39 let cap = capacity.max(1);
40 RingLog {
41 buf: (0..cap).map(|_| None).collect(),
42 capacity: cap,
43 head: 0,
44 count: 0,
45 seq: 0,
46 error_count: 0,
47 warn_count: 0,
48 }
49 }
50
51 pub fn push(&mut self, level: RingLogLevel, message: &str) {
52 match level {
53 RingLogLevel::Error => self.error_count += 1,
54 RingLogLevel::Warn => self.warn_count += 1,
55 _ => {}
56 }
57 let entry = RingLogEntry {
58 level,
59 message: message.to_string(),
60 seq: self.seq,
61 };
62 self.seq += 1;
63 let slot = self.head % self.capacity;
64 self.buf[slot] = Some(entry);
65 self.head += 1;
66 if self.count < self.capacity {
67 self.count += 1;
68 }
69 }
70
71 pub fn len(&self) -> usize {
72 self.count
73 }
74
75 pub fn is_empty(&self) -> bool {
76 self.count == 0
77 }
78
79 pub fn capacity(&self) -> usize {
80 self.capacity
81 }
82
83 pub fn error_count(&self) -> usize {
84 self.error_count
85 }
86
87 pub fn warn_count(&self) -> usize {
88 self.warn_count
89 }
90
91 pub fn entries(&self) -> Vec<&RingLogEntry> {
92 let start = if self.count < self.capacity {
93 0
94 } else {
95 self.head % self.capacity
96 };
97 let mut result = Vec::with_capacity(self.count);
98 for i in 0..self.count {
99 let idx = (start + i) % self.capacity;
100 if let Some(e) = &self.buf[idx] {
101 result.push(e);
102 }
103 }
104 result
105 }
106
107 pub fn last(&self) -> Option<&RingLogEntry> {
108 if self.count == 0 {
109 return None;
110 }
111 let idx = (self.head + self.capacity - 1) % self.capacity;
112 self.buf[idx].as_ref()
113 }
114
115 pub fn by_level(&self, level: &RingLogLevel) -> Vec<&RingLogEntry> {
116 self.entries()
117 .into_iter()
118 .filter(|e| &e.level == level)
119 .collect()
120 }
121
122 pub fn clear(&mut self) {
123 for slot in &mut self.buf {
124 *slot = None;
125 }
126 self.head = 0;
127 self.count = 0;
128 self.error_count = 0;
129 self.warn_count = 0;
130 }
131
132 pub fn total_written(&self) -> u64 {
133 self.seq
134 }
135}
136
137pub fn new_ring_log(capacity: usize) -> RingLog {
138 RingLog::new(capacity)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn push_and_len() {
147 let mut log = new_ring_log(10);
148 log.push(RingLogLevel::Info, "hello");
149 assert_eq!(log.len(), 1);
150 }
151
152 #[test]
153 fn wrap_around() {
154 let mut log = new_ring_log(3);
155 log.push(RingLogLevel::Info, "a");
156 log.push(RingLogLevel::Info, "b");
157 log.push(RingLogLevel::Info, "c");
158 log.push(RingLogLevel::Info, "d");
159 assert_eq!(log.len(), 3);
160 let entries = log.entries();
161 assert_eq!(entries.last().expect("should succeed").message, "d");
162 }
163
164 #[test]
165 fn error_count_tracked() {
166 let mut log = new_ring_log(10);
167 log.push(RingLogLevel::Error, "oops");
168 log.push(RingLogLevel::Warn, "meh");
169 assert_eq!(log.error_count(), 1);
170 assert_eq!(log.warn_count(), 1);
171 }
172
173 #[test]
174 fn last_entry() {
175 let mut log = new_ring_log(5);
176 log.push(RingLogLevel::Info, "first");
177 log.push(RingLogLevel::Debug, "last");
178 assert_eq!(log.last().expect("should succeed").message, "last");
179 }
180
181 #[test]
182 fn by_level_filter() {
183 let mut log = new_ring_log(10);
184 log.push(RingLogLevel::Info, "i");
185 log.push(RingLogLevel::Error, "e");
186 log.push(RingLogLevel::Info, "i2");
187 let errors = log.by_level(&RingLogLevel::Info);
188 assert_eq!(errors.len(), 2);
189 }
190
191 #[test]
192 fn clear_resets() {
193 let mut log = new_ring_log(5);
194 log.push(RingLogLevel::Error, "e");
195 log.clear();
196 assert!(log.is_empty());
197 assert_eq!(log.error_count(), 0);
198 }
199
200 #[test]
201 fn total_written_increases() {
202 let mut log = new_ring_log(2);
203 log.push(RingLogLevel::Info, "a");
204 log.push(RingLogLevel::Info, "b");
205 log.push(RingLogLevel::Info, "c");
206 assert_eq!(log.total_written(), 3);
207 }
208
209 #[test]
210 fn capacity_respected() {
211 let log = new_ring_log(8);
212 assert_eq!(log.capacity(), 8);
213 }
214
215 #[test]
216 fn seq_numbers_increment() {
217 let mut log = new_ring_log(10);
218 log.push(RingLogLevel::Info, "a");
219 log.push(RingLogLevel::Info, "b");
220 let entries = log.entries();
221 assert_eq!(entries[0].seq, 0);
222 assert_eq!(entries[1].seq, 1);
223 }
224}