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