1#![expect(missing_docs)]
10#![forbid(unsafe_code)]
11
12use std::fmt::Display;
13use std::time::Duration;
14use thiserror::Error;
15
16pub struct KmsgParsedEntry<'a> {
18 pub facility: u8,
20 pub level: u8,
22 pub seq: u64,
24 pub time: Duration,
26 pub message: EncodedMessage<'a>,
28}
29
30#[derive(Copy, Clone, Debug)]
32pub struct EncodedMessage<'a>(&'a str);
33
34impl<'a> EncodedMessage<'a> {
35 pub fn new(raw: &'a str) -> Self {
37 EncodedMessage(raw)
38 }
39
40 pub fn as_raw(&self) -> &str {
42 self.0
43 }
44}
45
46impl Display for EncodedMessage<'_> {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 let message = self.0.split('\n').next().unwrap();
49 let mut last = 0;
50 for (i, _) in message.match_indices('\\') {
51 write!(f, "{}", &message[last..i])?;
52 if message.as_bytes().get(i + 1) == Some(&b'\\') {
53 write!(f, "\\")?;
54 last = i + 2;
55 } else if let Some([b'x', escape @ ..]) = message.get(i + 1..i + 4).map(str::as_bytes) {
56 match escape {
58 b"1b" => write!(f, "\x1b")?,
59 _ => write!(f, "<{}>", &message[i + 2..i + 4])?,
60 }
61 last = i + 4;
62 } else {
63 last = i + 1;
64 }
65 }
66 write!(f, "{}", &message[last..])
67 }
68}
69
70#[derive(Debug, Error)]
72#[error("invalid kmsg entry")]
73pub struct InvalidKmsgEntry;
74
75impl<'a> KmsgParsedEntry<'a> {
76 pub fn new(data: &'a [u8]) -> Result<Self, InvalidKmsgEntry> {
77 Self::new_inner(data).ok_or(InvalidKmsgEntry)
78 }
79
80 fn new_inner(data: &'a [u8]) -> Option<Self> {
81 let s = std::str::from_utf8(data).ok()?;
82 let (kvs, message) = s.split_once(';')?;
83 let mut kvs = kvs.split(',');
84 let n: u32 = kvs.next()?.parse().ok()?;
85 let level = (n & 7) as u8;
86 let facility = (n >> 3) as u8;
87 let seq = kvs.next()?.parse().ok()?;
88 let time = Duration::from_micros(kvs.next()?.parse().ok()?);
89
90 Some(Self {
91 facility,
92 level,
93 seq,
94 time,
95 message: EncodedMessage(message),
96 })
97 }
98
99 pub fn display(&self, ansi: bool) -> KmsgEntryDisplay<'_> {
101 KmsgEntryDisplay { ansi, entry: self }
102 }
103}
104
105pub struct KmsgEntryDisplay<'a> {
107 ansi: bool,
108 entry: &'a KmsgParsedEntry<'a>,
109}
110
111impl Display for KmsgEntryDisplay<'_> {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 fmt_entry(
114 f,
115 self.ansi,
116 self.entry.level,
117 self.entry.time,
118 &self.entry.message,
119 )
120 }
121}
122
123fn fmt_entry(
124 f: &mut std::fmt::Formatter<'_>,
125 ansi: bool,
126 level: u8,
127 time: Duration,
128 message: &impl Display,
129) -> std::fmt::Result {
130 let time_sec = time.as_secs();
131 let time_usec = time.subsec_micros();
132
133 if !ansi {
134 return write!(f, "[{time_sec}.{time_usec:06}] {message}");
135 }
136
137 let red = "\x1b[0;31m";
138 let green = "\x1b[0;32m";
139 let yellow = "\x1b[0;33m";
140 let reset = "\x1b[0m";
141
142 write!(f, "{green}[{time_sec}.{time_usec:06}] ")?;
143
144 let message = message.to_string();
145 let mut message = message.as_str();
146 let mut target = None;
147 if let Some((s, rest)) = message.split_once(' ') {
148 if let Some(s) = s.strip_suffix(':') {
149 target = Some(s);
150 message = rest;
151 }
152 }
153
154 if let Some(target) = target {
155 write!(f, "{yellow}{target}: ")?;
156 }
157
158 let level_color = match level {
159 0..=3 => red,
160 4 => yellow,
161 5..=7 => reset,
162 _ => unreachable!(),
163 };
164
165 let message = message.trim();
166 write!(f, "{level_color}{message}{reset}")
167}
168
169pub struct SyslogParsedEntry<'a> {
171 pub _facility: u8,
172 pub level: u8,
173 pub time: Duration,
174 pub message: &'a str,
175}
176
177impl<'a> SyslogParsedEntry<'a> {
178 pub fn new(s: &'a str) -> Option<Self> {
180 let s = s.strip_prefix('<')?;
181 let (n, s) = s.split_once('>')?;
182 let n: u32 = n.parse().ok()?;
183 let level = (n & 7) as u8;
184 let facility = (n >> 3) as u8;
185 let s = s.strip_prefix('[')?;
186 let (secs, s) = s.trim_start().split_once('.')?;
187 let (usecs, s) = s.split_once(']')?;
188 let time = Duration::new(secs.parse().ok()?, usecs.parse::<u32>().ok()? * 1000);
189
190 Some(SyslogParsedEntry {
191 _facility: facility,
192 level,
193 time,
194 message: s.trim(),
195 })
196 }
197
198 pub fn display(&self, ansi: bool) -> SyslogEntryDisplay<'_> {
200 SyslogEntryDisplay { ansi, entry: self }
201 }
202}
203
204pub struct SyslogEntryDisplay<'a> {
206 ansi: bool,
207 entry: &'a SyslogParsedEntry<'a>,
208}
209
210impl Display for SyslogEntryDisplay<'_> {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 fmt_entry(
213 f,
214 self.ansi,
215 self.entry.level,
216 self.entry.time,
217 &self.entry.message,
218 )
219 }
220}