kmsg/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Types for parsing /dev/kmsg
5//!
6//! See <https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg> for
7//! documentation on the kmsg entry format.
8
9#![expect(missing_docs)]
10#![forbid(unsafe_code)]
11
12use std::fmt::Display;
13use std::time::Duration;
14use thiserror::Error;
15
16/// A parsed kmsg entry.
17pub struct KmsgParsedEntry<'a> {
18    /// The facility.
19    pub facility: u8,
20    /// The message level.
21    pub level: u8,
22    /// The sequence number.
23    pub seq: u64,
24    /// The time of the message since boot.
25    pub time: Duration,
26    /// The encoded message.
27    pub message: EncodedMessage<'a>,
28}
29
30/// An encoded message.
31#[derive(Copy, Clone, Debug)]
32pub struct EncodedMessage<'a>(&'a str);
33
34impl<'a> EncodedMessage<'a> {
35    /// Creates a new encoded message from a raw string.
36    pub fn new(raw: &'a str) -> Self {
37        EncodedMessage(raw)
38    }
39
40    /// The raw encoded string.
41    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                // Allow escaped ESC for ANSI color.
57                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/// An error indicating the kmsg entry could not be parsed because it is invalid.
71#[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    /// Returns a [`Display`] implementation that includes colors if `ansi`.
100    pub fn display(&self, ansi: bool) -> KmsgEntryDisplay<'_> {
101        KmsgEntryDisplay { ansi, entry: self }
102    }
103}
104
105/// [`Display`] implementation for [`SyslogParsedEntry`].
106pub 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
169/// A parsed syslog-format entry.
170pub 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    /// Parses an entry that looks like: `<n>[   3.593853] target: message`.
179    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    /// Returns a [`Display`] implementation that includes colors if `ansi`.
199    pub fn display(&self, ansi: bool) -> SyslogEntryDisplay<'_> {
200        SyslogEntryDisplay { ansi, entry: self }
201    }
202}
203
204/// [`Display`] implementation for [`SyslogParsedEntry`].
205pub 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}