flams_utils/
logs.rs

1use std::fmt::Display;
2use std::num::NonZeroU64;
3use std::str::FromStr;
4
5use crate::time::Timestamp;
6use crate::vecmap::VecMap;
7
8#[derive(Clone,Debug)]
9#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
10pub enum LogFileLine {
11    SpanOpen {
12        name:String,
13        id:NonZeroU64,
14        timestamp:Timestamp,
15        target:Option<String>,
16        level:LogLevel,
17        args:VecMap<String, String>,
18        parent:Option<NonZeroU64>
19    },
20    SpanClose {
21        id:NonZeroU64,
22        timestamp:Timestamp
23    },
24    Message {
25        message:String,
26        timestamp:Timestamp,
27        target:Option<String>,
28        level:LogLevel,
29        args:VecMap<String, String>,
30        span: Option<NonZeroU64>
31    },
32}
33
34
35#[derive(Debug,Clone)]
36#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
37pub struct LogMessage {
38    pub message:String,
39    pub timestamp:Timestamp,
40    pub target:Option<String>,
41    pub level:LogLevel,
42    pub args:VecMap<String, String>,
43}
44
45#[derive(Debug,Clone)]
46#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
47pub struct LogSpan {
48    pub name:String,
49    pub timestamp:Timestamp,
50    pub target:Option<String>,
51    pub level:LogLevel,
52    pub args:VecMap<String, String>,
53    pub children:Vec<LogTreeElem>,
54    pub closed:Option<Timestamp>
55}
56
57#[derive(Debug,Clone)]
58#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
59pub enum LogTreeElem {
60    Span(LogSpan),
61    Message(LogMessage)
62}
63impl From<LogMessage> for LogTreeElem {
64    #[inline]
65    fn from(value: LogMessage) -> Self {
66        Self::Message(value)
67    }
68}
69impl From<LogSpan> for LogTreeElem {
70    #[inline]
71    fn from(value: LogSpan) -> Self {
72        Self::Span(value)
73    }
74}
75
76#[derive(Debug,Clone,Default)]
77#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
78pub struct LogTree {
79    pub children:Vec<LogTreeElem>,
80    pub open_span_paths:VecMap<NonZeroU64,Vec<usize>>
81}
82
83
84impl LogTree {
85    fn merge(&mut self,line:LogTreeElem, parent:Option<NonZeroU64>,is_span:Option<NonZeroU64>) {
86        let mut path = Vec::new();
87        let p = if let Some(p) = parent.as_ref().and_then(|p| self.open_span_paths.get(p)) {
88            if is_span.is_some() {path.clone_from(p);}
89            let mut ls = &mut self.children;
90            for i in p {
91                let LogTreeElem::Span(s) = &mut ls[*i] else {
92                    unreachable!();
93                };
94                ls = &mut s.children;
95            }
96            ls
97        } else {
98            /*if parent.is_some() {
99                println!("Parent not found: {line:?}!");
100            }*/
101            &mut self.children
102        };
103        if let Some(id) = is_span {
104            path.push(p.len());
105            self.open_span_paths.insert(id,path);
106        }
107        p.push(line);
108    }
109    fn close(&mut self,id:NonZeroU64,timestamp:Timestamp) {
110        let e = if let Some(mut path) = self.open_span_paths.remove(&id) {
111            let mut ls = &mut self.children;
112            let last = path.pop().unwrap_or_else(|| unreachable!());
113            for i in path {
114                let LogTreeElem::Span(s) = &mut ls[i] else {
115                    unreachable!();
116                };
117                ls = &mut s.children;
118            }
119            &mut ls[last]
120        } else { return };
121        let LogTreeElem::Span(e) = e else {unreachable!()};
122        e.closed = Some(timestamp);
123    }
124    pub fn add_line(&mut self,line:LogFileLine) {
125        match line {
126            LogFileLine::SpanOpen { name, id, timestamp, target, level, args, parent } => {
127                let span = LogSpan {
128                    name,timestamp,target,level,args,
129                    children: Vec::new(),
130                    closed: None
131                };
132                self.merge(span.into(), parent,Some(id));
133            }
134            LogFileLine::Message { message, timestamp, target, level, args, span } => {
135                let message = LogMessage {
136                    message, timestamp, target, level,args
137                };
138                self.merge(message.into(), span,None);
139            }
140            LogFileLine::SpanClose { timestamp,id, .. } => {
141                self.close(id,timestamp);
142            }
143        }
144    }
145}
146
147
148#[derive(Debug,Copy,Clone,PartialEq,Eq)]
149#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
150pub enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR }
151impl FromStr for LogLevel {
152    type Err = ();
153    fn from_str(s: &str) -> Result<Self, Self::Err> {
154        match s {
155            "TRACE" => Ok(Self::TRACE),
156            "DEBUG" => Ok(Self::DEBUG),
157            "INFO" => Ok(Self::INFO),
158            "WARN" => Ok(Self::WARN),
159            "ERROR" => Ok(Self::ERROR),
160            _ => Err(())
161        }
162    }
163}
164impl From<tracing::Level> for LogLevel {
165    fn from(l:tracing::Level) -> Self {
166        match l {
167            tracing::Level::TRACE => Self::TRACE,
168            tracing::Level::DEBUG => Self::DEBUG,
169            tracing::Level::INFO => Self::INFO,
170            tracing::Level::WARN => Self::WARN,
171            tracing::Level::ERROR => Self::ERROR
172        }
173    }
174}
175impl PartialOrd for LogLevel {
176    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
177        Some(self.cmp(other))
178    }
179}
180impl Ord for LogLevel {
181    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
182        match (self, other) {
183            (a,b) if a == b => std::cmp::Ordering::Equal,
184            (Self::TRACE, _) => std::cmp::Ordering::Less,
185            (_, Self::TRACE) => std::cmp::Ordering::Greater,
186            (Self::DEBUG, _) => std::cmp::Ordering::Less,
187            (_, Self::DEBUG) => std::cmp::Ordering::Greater,
188            (Self::INFO, _) => std::cmp::Ordering::Less,
189            (_, Self::INFO) => std::cmp::Ordering::Greater,
190            (Self::WARN, _) => std::cmp::Ordering::Less,
191            (_, Self::WARN) => std::cmp::Ordering::Greater,
192            _ => unreachable!()
193        }
194    }
195}
196impl Display for LogLevel {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        match self {
199            Self::TRACE => write!(f,"TRACE"),
200            Self::DEBUG => write!(f,"DEBUG"),
201            Self::INFO =>  write!(f,"INFO "),
202            Self::WARN =>  write!(f,"WARN "),
203            Self::ERROR => write!(f,"ERROR")
204        }
205    }
206}
207
208impl LogTreeElem {
209    
210    #[must_use]
211    pub const fn timestamp(&self) -> Timestamp {
212        match self {
213            Self::Span(LogSpan {timestamp,..}) |
214            Self::Message(LogMessage {timestamp,..}) => *timestamp
215        }
216    }
217    #[must_use]
218    pub const fn level(&self) -> LogLevel {
219        match self {
220            Self::Span(LogSpan {level,..}) |
221            Self::Message(LogMessage {level,..}) => *level
222        }
223    }
224}
225
226/*
227
228    #[must_use]
229    pub fn target(&self) -> Option<&str> {
230        match self {
231            Self::Span(LogSpan {target,..}) |
232            Self::Message(LogMessage {target,..}) => target.as_deref()
233        }
234    }
235    #[must_use]
236    pub const fn args(&self) -> &VecMap<String, String> {
237        match self {
238            Self::Span(LogSpan {args,..}) |
239            Self::Message(LogMessage {args,..}) => args
240        }
241    }
242}
243
244#[derive(Debug,Clone)]
245#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
246pub struct LogMessage {
247    pub message:String,
248    pub timestamp:Timestamp,
249    pub target:Option<String>,
250    pub level:LogLevel,
251    pub args:VecMap<String, String>,
252}
253
254#[derive(Debug,Clone)]
255#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
256pub struct LogSpan {
257    pub name:String,
258    pub timestamp:Timestamp,
259    pub target:Option<String>,
260    pub level:LogLevel,
261    pub args:VecMap<String, String>,
262    pub children:Vec<LogTreeElem>,
263    pub closed:Option<Timestamp>
264}
265
266impl<S:ToString+PartialEq<String>,I:IntoIterator<Item = LogFileLine<S>>> From<I> for LogTree {
267    fn from(iter: I) -> Self {
268        let mut s = Self{children:Vec::new(),open_span_paths:VecMap::default()};
269        for e in iter {
270            s.add_line(e);
271        }
272        s
273    }
274}
275
276#[derive(Clone,Debug)]
277#[cfg_attr(feature="serde", derive(serde::Serialize,serde::Deserialize))]
278pub enum LogFileLine<S> {
279    SpanOpen {
280        name:S,
281        timestamp:Timestamp,
282        target:Option<S>,
283        level:LogLevel,
284        args:VecMap<S, S>,
285        parent:Option<String>
286    },
287    SpanClose {
288        id:String,
289        timestamp:Timestamp,
290        parent:Option<String>
291    },
292    Message {
293        message:S,
294        timestamp:Timestamp,
295        target:Option<S>,
296        level:LogLevel,
297        args:VecMap<S, S>,
298        span: Option<String>
299    },
300}
301impl<S:AsRef<str> + std::fmt::Debug + Hash> LogFileLine<S> {
302
303    #[must_use]
304    pub fn id_from(msg:&str,args:&VecMap<S,S>) -> String {
305        hashstr("", &(msg,args))
306    }
307    #[must_use]
308    pub fn id(&self) -> String {
309        match self {
310            Self::SpanOpen { name, args, .. } => {
311                Self::id_from(name.as_ref(),args)
312            }
313            Self::SpanClose { id, .. } => id.clone(),
314            Self::Message { message, args, .. } => {
315                Self::id_from(message.as_ref(),args)
316            }
317        }
318    }
319}
320
321impl<'a> LogFileLine<&'a str> {
322    fn read_string(parser:&mut ParseStr<'a,()>) -> Option<&'a str> {
323        if !parser.drop_prefix("\"") {return None}
324        let s = parser.read_until_escaped('\"','\\');
325        parser.pop_head();
326        Some(s)
327    }
328    fn read_elem(parser:&mut ParseStr<'a,()>) -> Option<&'a str> {
329        if parser.peek_head().is_some_and(|c| c == '\"') {
330            Self::read_string(parser)
331        } else if parser.peek_head().is_some_and(|c| c.is_ascii_digit()) {
332            Some(parser.read_while(|c| c.is_ascii_digit() || c == '.'))
333        } else {
334            None
335        }
336    }
337    fn read_span(parser:&mut ParseStr<'a,()>) -> Option<(&'a str,VecMap<&'a str,&'a str>)> {
338        let mut name = "";
339        if !parser.drop_prefix("{") {return None}
340        let mut vec = VecMap::default();
341        while parser.drop_prefix("\"") {
342            let key = parser.read_until(|c| c == '"');
343            if !parser.drop_prefix("\":") {return None}
344            if key == "name" {
345                name = Self::read_string(parser)?;
346            } else {
347                vec.insert(key,Self::read_elem(parser)?);
348            }
349            parser.drop_prefix(",");
350        }
351        if !parser.drop_prefix("}") {return None}
352        Some((name,vec))
353    }
354
355    #[must_use]
356    pub fn parse(s: &'a str) -> Option<Self> {
357        let mut parser = ParseStr::<()>::new(s);
358        if !parser.drop_prefix("{\"timestamp\":") {return None}
359        let ts = Self::read_string(&mut parser)?;
360        let timestamp =ts.parse().ok()?;
361        if !parser.drop_prefix(",\"level\":") {return None}
362        let level = Self::read_string(&mut parser)?;
363        let level : LogLevel = level.parse().ok()?;
364        if !parser.drop_prefix(",\"message\":") {return None}
365        let message = Self::read_string(&mut parser)?;
366        let mut target = None;
367        let mut span = None;
368        let mut spans = Vec::new();
369        let mut args = VecMap::default();
370        while parser.drop_prefix(",\"") {
371            let key = parser.read_until(|c| c == '"');
372            if !parser.drop_prefix("\":") {return None}
373            match key {
374                "target" => {
375                    target = Some(Self::read_string(&mut parser)?);
376                }
377                "span" => span = Some(Self::read_span(&mut parser)?),
378                "spans" => {
379                    if !parser.drop_prefix("[") {return None}
380                    if parser.peek_head() == Some('{') {
381                        spans.push(Self::read_span(&mut parser)?);
382                        while parser.drop_prefix(",") {
383                            spans.push(Self::read_span(&mut parser)?);
384                        }
385                    }
386                    if !parser.drop_prefix("]") {return None}
387                }
388                "time.busy" | "time.idle" => {
389                    Self::read_string(&mut parser)?;
390                }
391                k => {
392                    args.insert(k,Self::read_elem(&mut parser)?);
393                }
394            }
395        }
396        if !parser.drop_prefix("}") {return None}
397        if message == "new" {
398            let (name,args) = span?;
399            Some(LogFileLine::SpanOpen {
400                name,
401                timestamp,
402                target,
403                level,
404                args,
405                parent:spans.into_iter().map(|(name,args)| hashstr("",&(name,args))).next_back()
406            })
407        } else if message == "close" {
408            let (name,args) = span?;
409            let id = LogFileLine::id_from(name,&args);
410            Some(LogFileLine::SpanClose {
411                id,
412                timestamp,
413                parent:spans.into_iter().map(|(name,args)| hashstr("",&(name,args))).next_back()
414            })
415        } else {
416            Some(LogFileLine::Message {
417                message,
418                timestamp,
419                target,
420                level,
421                args,
422                span:spans.into_iter().map(|(name,args)| hashstr("",&(name,args))).next_back()
423            })
424        }
425    }
426
427    #[must_use]
428    pub fn to_owned(self) -> LogFileLine<String> {
429        match self {
430            LogFileLine::SpanOpen { name, timestamp, target, level, args, parent } => LogFileLine::SpanOpen {
431                name: name.into(),
432                timestamp,
433                target: target.map(Into::into),
434                level,
435                args: args.into_iter().map(|(k,v)| (k.into(),v.into())).collect(),
436                parent
437            },
438            LogFileLine::SpanClose { id, timestamp, parent } => LogFileLine::SpanClose {
439                id, timestamp,
440                parent
441            },
442            LogFileLine::Message { message, timestamp, target, level, args, span } => LogFileLine::Message {
443                message: message.into(),
444                timestamp,
445                target: target.map(Into::into),
446                level,
447                args: args.into_iter().map(|(k,v)| (k.into(),v.into())).collect(),
448                span
449            }
450        }
451    }
452}
453
454
455     */