olai_stack_runtime/
logs.rs1use serde::Serialize;
4
5#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
7pub struct LogLine {
8 pub container: String,
10 pub timestamp: Option<String>,
12 pub text: String,
14}
15
16pub fn parse_log_line(line: &str) -> LogLine {
20 let Some((container, rest)) = line.split_once(" | ").or_else(|| line.split_once(" |")) else {
21 return LogLine {
22 container: String::new(),
23 timestamp: None,
24 text: line.to_owned(),
25 };
26 };
27 let (timestamp, text) = match rest.split_once(' ') {
28 Some((ts, text)) if looks_like_timestamp(ts) => (Some(ts.to_owned()), text),
29 _ if looks_like_timestamp(rest) => (Some(rest.to_owned()), ""),
30 _ => (None, rest),
31 };
32 LogLine {
33 container: container.trim().to_owned(),
34 timestamp,
35 text: text.to_owned(),
36 }
37}
38
39fn looks_like_timestamp(s: &str) -> bool {
41 s.len() >= 20 && s.as_bytes()[..4].iter().all(u8::is_ascii_digit) && s.as_bytes()[10] == b'T'
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn a_line_without_a_timestamp_keeps_its_text() {
50 let line = parse_log_line("web-1 | hello there");
51 assert_eq!(line.container, "web-1");
52 assert_eq!(line.timestamp, None);
53 assert_eq!(line.text, "hello there");
54 }
55
56 #[test]
57 fn a_bare_line_has_no_container() {
58 let line = parse_log_line("no prefix here");
59 assert_eq!(line.container, "");
60 assert_eq!(line.text, "no prefix here");
61 }
62}