rmesg/
entry.rs

1// Copyright (c) 2019 Polyverse Corporation
2
3use num_derive::FromPrimitive;
4use std::error::Error;
5use std::fmt::{Display, Error as FmtError, Formatter, Result as FmtResult, Write};
6use std::time::Duration;
7use strum_macros::{Display, EnumString};
8
9#[cfg(feature = "extra-traits")]
10use serde::{Deserialize, Serialize};
11
12/// A parsed/structured entry from kernel log buffer
13#[derive(PartialEq, Debug, Clone)]
14pub struct Entry {
15    // Log facility
16    pub facility: Option<LogFacility>,
17
18    // Log level
19    pub level: Option<LogLevel>,
20
21    // Log sequence number
22    pub sequence_num: Option<usize>,
23
24    // The amount of time since system bootstrapped
25    pub timestamp_from_system_start: Option<Duration>,
26
27    // Log message
28    pub message: String,
29}
30
31impl Entry {
32    pub fn to_faclev(&self) -> Option<u8> {
33        match (self.facility, self.level) {
34            (Some(facility), Some(level)) => Some(((facility as u8) << 3) + (level as u8)),
35            _ => None,
36        }
37    }
38
39    // Like so:
40    // <5>a.out[4054]: segfault at 7ffd5503d358 ip 00007ffd5503d358 sp 00007ffd5503d258 error 15
41    // OR
42    // <5>[   233434.343533] a.out[4054]: segfault at 7ffd5503d358 ip 00007ffd5503d358 sp 00007ffd5503d258 error 15
43    pub fn to_klog_str(&self) -> Result<String, FmtError> {
44        if let Some(faclev) = self.to_faclev() {
45            // +6 for buffer + capacity is 16+6 (for timestamp) + 2 (for []) + 2 (for <>) + 1 for facllev + message
46            let mut retstr = String::with_capacity(35 + self.message.len());
47
48            write!(retstr, "<{}>", faclev)?;
49
50            if let Some(ts) = self.timestamp_from_system_start {
51                write!(retstr, "[{: >16.6}]", ts.as_secs_f64())?;
52            }
53
54            write!(retstr, "{}", self.message)?;
55
56            Ok(retstr)
57        } else {
58            Ok(self.message.to_owned())
59        }
60    }
61
62    // Like so:
63    // 6,1,0,-;Command, line: BOOT_IMAGE=/boot/kernel console=ttyS0 console=ttyS1 page_poison=1 vsyscall=emulate panic=1 root=/dev/sr0 text
64    //  LINE2=foobar
65    //  LINE 3 = foobar ; with semicolon
66    pub fn to_kmsg_str(&self) -> Result<String, FmtError> {
67        if let Some(faclev) = self.to_faclev() {
68            // +7 for buffer + capacity is 12 (for timestamp) + 5 (for punctuations) + 1 for facllev + message
69            let mut retstr = String::with_capacity(25 + self.message.len());
70
71            let sequence_num = self.sequence_num.unwrap_or(0);
72            write!(retstr, "{},{},", faclev, sequence_num)?;
73
74            if let Some(ts) = self.timestamp_from_system_start {
75                write!(retstr, "{},-;", ts.as_micros())?;
76            } else {
77                retstr.push_str("0,-;");
78            }
79
80            write!(retstr, "{}", self.message)?;
81
82            Ok(retstr)
83        } else {
84            Ok(self.message.to_string())
85        }
86    }
87}
88
89impl Display for Entry {
90    fn fmt(&self, f: &mut Formatter) -> FmtResult {
91        if let Some(ts) = self.timestamp_from_system_start {
92            write!(f, "[{: >16.6}] ", ts.as_secs_f64())?
93        }
94
95        write!(f, "{}", self.message)
96    }
97}
98
99/// Linux kmesg (kernel message buffer) Log Facility.
100#[cfg_attr(feature = "extra-traits", derive(Serialize, Deserialize))]
101#[derive(EnumString, Debug, PartialEq, Display, Copy, Clone, FromPrimitive)]
102pub enum LogFacility {
103    #[strum(serialize = "kern")]
104    Kern = 0,
105
106    #[strum(serialize = "user")]
107    User,
108
109    #[strum(serialize = "mail")]
110    Mail,
111
112    #[strum(serialize = "daemon")]
113    Daemon,
114
115    #[strum(serialize = "auth")]
116    Auth,
117
118    #[strum(serialize = "syslog")]
119    Syslog,
120
121    #[strum(serialize = "lpr")]
122    Lpr,
123
124    #[strum(serialize = "news")]
125    News,
126
127    #[strum(serialize = "uucp")]
128    UUCP,
129
130    #[strum(serialize = "cron")]
131    Cron,
132
133    #[strum(serialize = "authpriv")]
134    AuthPriv,
135
136    #[strum(serialize = "ftp")]
137    FTP,
138}
139
140/// Linux kmesg (kernel message buffer) Log Level.
141#[cfg_attr(feature = "extra-traits", derive(Serialize, Deserialize))]
142#[derive(EnumString, Debug, PartialEq, Display, Copy, Clone, FromPrimitive)]
143pub enum LogLevel {
144    #[strum(serialize = "emerg")]
145    Emergency = 0,
146
147    #[strum(serialize = "alert")]
148    Alert,
149
150    #[strum(serialize = "crit")]
151    Critical,
152
153    #[strum(serialize = "err")]
154    Error,
155
156    #[strum(serialize = "warn")]
157    Warning,
158
159    #[strum(serialize = "notice")]
160    Notice,
161
162    #[strum(serialize = "info")]
163    Info,
164
165    #[strum(serialize = "debug")]
166    Debug,
167}
168
169#[derive(Debug)]
170pub enum EntryParsingError {
171    Completed,
172    EventTooOld,
173    Generic(String),
174}
175impl Error for EntryParsingError {}
176impl Display for EntryParsingError {
177    fn fmt(&self, f: &mut Formatter) -> FmtResult {
178        write!(
179            f,
180            "KMsgParsingError:: {}",
181            match self {
182                Self::Completed => "Completed Parsing",
183                Self::EventTooOld =>
184                    "Event too old due to timestamp or sequence number (we've parsed newer messages than these)",
185                Self::Generic(s) => s,
186            }
187        )
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_serialize_to_klog() {
197        let entry_struct = Entry {
198            timestamp_from_system_start: Some(Duration::from_secs_f64(24241.325252)),
199            facility: Some(LogFacility::Kern),
200            level: Some(LogLevel::Info),
201            sequence_num: Some(10),
202            message: "Test message".to_owned(),
203        };
204        let expected_serialization = "<6>[    24241.325252]Test message";
205
206        let boxed_entry_struct = Box::new(entry_struct.clone());
207
208        let printed_entry_struct = entry_struct.to_klog_str().unwrap();
209        assert_eq!(printed_entry_struct, expected_serialization);
210
211        let printed_boxed_entry_struct = boxed_entry_struct.to_klog_str().unwrap();
212        assert_eq!(printed_boxed_entry_struct, expected_serialization);
213    }
214
215    #[test]
216    fn test_serialize_to_kmsg() {
217        let entry_struct = Entry {
218            timestamp_from_system_start: Some(Duration::from_secs_f64(24241.325252)),
219            facility: Some(LogFacility::Kern),
220            level: Some(LogLevel::Info),
221            sequence_num: Some(23),
222            message: "Test message".to_owned(),
223        };
224        let expected_serialization = "6,23,24241325252,-;Test message";
225
226        let boxed_entry_struct = Box::new(entry_struct.clone());
227
228        let printed_entry_struct = entry_struct.to_kmsg_str().unwrap();
229        assert_eq!(printed_entry_struct, expected_serialization);
230
231        let printed_boxed_entry_struct = boxed_entry_struct.to_kmsg_str().unwrap();
232        assert_eq!(printed_boxed_entry_struct, expected_serialization);
233    }
234
235    #[test]
236    fn test_display() {
237        let entry_struct = Entry {
238            timestamp_from_system_start: Some(Duration::from_secs_f64(24241.325252)),
239            facility: Some(LogFacility::Kern),
240            level: Some(LogLevel::Info),
241            sequence_num: Some(15),
242            message: "Test message".to_owned(),
243        };
244        let expected_serialization = "[    24241.325252] Test message";
245
246        let boxed_entry_struct = Box::new(entry_struct.clone());
247
248        let printed_entry_struct = format!("{}", entry_struct);
249        assert_eq!(printed_entry_struct, expected_serialization);
250
251        let printed_boxed_entry_struct = format!("{}", boxed_entry_struct);
252        assert_eq!(printed_boxed_entry_struct, expected_serialization);
253    }
254}