1use crate::value::Value;
6use std::time::Instant;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub enum TraceLevel {
11 Debug = 0,
12 Info = 1,
13 Warn = 2,
14 Error = 3,
15}
16
17impl TraceLevel {
18 pub fn as_str(&self) -> &'static str {
20 match self {
21 Self::Debug => "DEBUG",
22 Self::Info => "INFO",
23 Self::Warn => "WARN",
24 Self::Error => "ERROR",
25 }
26 }
27}
28
29impl std::str::FromStr for TraceLevel {
30 type Err = String;
31
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 match s.to_lowercase().as_str() {
34 "debug" => Ok(Self::Debug),
35 "info" => Ok(Self::Info),
36 "warn" | "warning" => Ok(Self::Warn),
37 "error" => Ok(Self::Error),
38 _ => Err(format!("Invalid trace level: {}", s)),
39 }
40 }
41}
42
43impl std::fmt::Display for TraceLevel {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "{}", self.as_str())
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct TraceEntry {
52 pub timestamp: Instant,
54 pub level: TraceLevel,
56 pub category: String,
58 pub label: Option<String>,
60 pub values: Vec<Value>,
62 pub location: Option<String>,
64}
65
66impl TraceEntry {
67 pub fn new(level: TraceLevel, category: String, values: Vec<Value>) -> Self {
69 Self {
70 timestamp: Instant::now(),
71 level,
72 category,
73 label: None,
74 values,
75 location: None,
76 }
77 }
78
79 pub fn with_label(mut self, label: String) -> Self {
81 self.label = Some(label);
82 self
83 }
84
85 pub fn with_location(mut self, location: String) -> Self {
87 self.location = Some(location);
88 self
89 }
90
91 pub fn format(&self) -> String {
93 let values_str: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
94 let payload = values_str.join(" ");
95
96 match &self.label {
97 Some(l) => format!("[{}:{}:{}] {}", self.level, self.category, l, payload),
98 None => format!("[{}:{}] {}", self.level, self.category, payload),
99 }
100 }
101}
102
103#[derive(Debug, Default, Clone)]
105pub struct TraceFilter {
106 pub min_level: Option<TraceLevel>,
108 pub category: Option<String>,
110 pub label: Option<String>,
112 pub since: Option<Instant>,
114}
115
116impl TraceFilter {
117 pub fn new() -> Self {
119 Self::default()
120 }
121
122 pub fn with_min_level(mut self, level: TraceLevel) -> Self {
124 self.min_level = Some(level);
125 self
126 }
127
128 pub fn with_category(mut self, category: String) -> Self {
130 self.category = Some(category);
131 self
132 }
133
134 pub fn with_label(mut self, label: String) -> Self {
136 self.label = Some(label);
137 self
138 }
139
140 pub fn with_since(mut self, since: Instant) -> Self {
142 self.since = Some(since);
143 self
144 }
145
146 pub fn matches(&self, entry: &TraceEntry) -> bool {
148 if let Some(min_level) = self.min_level
149 && entry.level < min_level
150 {
151 return false;
152 }
153
154 if let Some(ref category) = self.category
155 && &entry.category != category
156 {
157 return false;
158 }
159
160 if let Some(ref label) = self.label
161 && entry.label.as_ref() != Some(label)
162 {
163 return false;
164 }
165
166 if let Some(since) = self.since
167 && entry.timestamp < since
168 {
169 return false;
170 }
171
172 true
173 }
174}
175
176#[derive(Debug, Clone, Default)]
178pub struct TraceStats {
179 pub total_entries: usize,
181 pub by_level: std::collections::HashMap<TraceLevel, usize>,
183 pub by_category: std::collections::HashMap<String, usize>,
185 pub buffer_size: usize,
187 pub buffer_full: bool,
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use std::str::FromStr;
195
196 #[test]
197 fn test_trace_level_from_str() {
198 assert_eq!(TraceLevel::from_str("debug"), Ok(TraceLevel::Debug));
199 assert_eq!(TraceLevel::from_str("DEBUG"), Ok(TraceLevel::Debug));
200 assert_eq!(TraceLevel::from_str("info"), Ok(TraceLevel::Info));
201 assert_eq!(TraceLevel::from_str("warn"), Ok(TraceLevel::Warn));
202 assert_eq!(TraceLevel::from_str("warning"), Ok(TraceLevel::Warn));
203 assert_eq!(TraceLevel::from_str("error"), Ok(TraceLevel::Error));
204 assert!(TraceLevel::from_str("invalid").is_err());
205 }
206
207 #[test]
208 fn test_trace_level_display() {
209 assert_eq!(TraceLevel::Debug.to_string(), "DEBUG");
210 assert_eq!(TraceLevel::Info.to_string(), "INFO");
211 assert_eq!(TraceLevel::Warn.to_string(), "WARN");
212 assert_eq!(TraceLevel::Error.to_string(), "ERROR");
213 }
214
215 #[test]
216 fn test_trace_level_ordering() {
217 assert!(TraceLevel::Debug < TraceLevel::Info);
218 assert!(TraceLevel::Info < TraceLevel::Warn);
219 assert!(TraceLevel::Warn < TraceLevel::Error);
220 }
221
222 #[test]
223 fn test_trace_entry_creation() {
224 let entry = TraceEntry::new(
225 TraceLevel::Info,
226 "test_category".to_string(),
227 vec![Value::Number(42.0)],
228 );
229
230 assert_eq!(entry.level, TraceLevel::Info);
231 assert_eq!(entry.category, "test_category");
232 assert_eq!(entry.values.len(), 1);
233 assert!(entry.label.is_none());
234 assert!(entry.location.is_none());
235 }
236
237 #[test]
238 fn test_trace_entry_with_label() {
239 let entry = TraceEntry::new(TraceLevel::Info, "test_category".to_string(), vec![])
240 .with_label("test_label".to_string());
241
242 assert_eq!(entry.label, Some("test_label".to_string()));
243 }
244
245 #[test]
246 fn test_trace_entry_format() {
247 let entry1 = TraceEntry::new(
249 TraceLevel::Info,
250 "category1".to_string(),
251 vec![Value::Number(42.0)],
252 );
253 let formatted1 = entry1.format();
254 assert!(formatted1.contains("[INFO:category1]"));
255 assert!(formatted1.contains("42"));
256
257 let entry2 = TraceEntry::new(
259 TraceLevel::Error,
260 "category2".to_string(),
261 vec![Value::String("error_msg".to_string())],
262 )
263 .with_label("test_label".to_string());
264 let formatted2 = entry2.format();
265 assert!(formatted2.contains("[ERROR:category2:test_label]"));
266 assert!(formatted2.contains("error_msg"));
267 }
268
269 #[test]
270 fn test_trace_filter() {
271 let filter = TraceFilter::new().with_min_level(TraceLevel::Warn);
272
273 let debug_entry = TraceEntry::new(TraceLevel::Debug, "test".to_string(), vec![]);
274 let warn_entry = TraceEntry::new(TraceLevel::Warn, "test".to_string(), vec![]);
275 let error_entry = TraceEntry::new(TraceLevel::Error, "test".to_string(), vec![]);
276
277 assert!(!filter.matches(&debug_entry));
278 assert!(filter.matches(&warn_entry));
279 assert!(filter.matches(&error_entry));
280 }
281
282 #[test]
283 fn test_trace_filter_with_category() {
284 let filter = TraceFilter::new().with_category("api_call".to_string());
285
286 let api_entry = TraceEntry::new(TraceLevel::Info, "api_call".to_string(), vec![]);
287 let db_entry = TraceEntry::new(TraceLevel::Info, "database".to_string(), vec![]);
288
289 assert!(filter.matches(&api_entry));
290 assert!(!filter.matches(&db_entry));
291 }
292
293 #[test]
294 fn test_trace_filter_with_label() {
295 let filter = TraceFilter::new().with_label("slow_request".to_string());
296
297 let entry1 = TraceEntry::new(TraceLevel::Warn, "api".to_string(), vec![])
298 .with_label("slow_request".to_string());
299
300 let entry2 = TraceEntry::new(TraceLevel::Warn, "api".to_string(), vec![])
301 .with_label("fast_request".to_string());
302
303 assert!(filter.matches(&entry1));
304 assert!(!filter.matches(&entry2));
305 }
306
307 #[test]
308 fn test_trace_filter_combined() {
309 let filter = TraceFilter::new()
310 .with_min_level(TraceLevel::Warn)
311 .with_category("api".to_string());
312
313 let entry1 = TraceEntry::new(TraceLevel::Warn, "api".to_string(), vec![]);
315
316 let entry2 = TraceEntry::new(TraceLevel::Info, "api".to_string(), vec![]);
318
319 let entry3 = TraceEntry::new(TraceLevel::Warn, "database".to_string(), vec![]);
321
322 assert!(filter.matches(&entry1));
323 assert!(!filter.matches(&entry2));
324 assert!(!filter.matches(&entry3));
325 }
326}