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