1use std::collections::BTreeMap;
4use std::fmt::Display;
5use std::io::Write;
6
7use crate::errors::*;
8use crate::facility::Facility;
9use crate::{Priority, get_hostname, get_process_info};
10
11#[allow(non_camel_case_types)]
12#[derive(Copy, Clone)]
13pub enum Severity {
14 LOG_EMERG,
15 LOG_ALERT,
16 LOG_CRIT,
17 LOG_ERR,
18 LOG_WARNING,
19 LOG_NOTICE,
20 LOG_INFO,
21 LOG_DEBUG,
22}
23
24pub trait LogFormat<T> {
25 fn format<W: Write>(&self, w: &mut W, severity: Severity, message: T) -> Result<()>;
26
27 fn emerg<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
28 self.format(w, Severity::LOG_EMERG, message)
29 }
30
31 fn alert<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
32 self.format(w, Severity::LOG_ALERT, message)
33 }
34
35 fn crit<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
36 self.format(w, Severity::LOG_CRIT, message)
37 }
38
39 fn err<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
40 self.format(w, Severity::LOG_ERR, message)
41 }
42
43 fn warning<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
44 self.format(w, Severity::LOG_WARNING, message)
45 }
46
47 fn notice<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
48 self.format(w, Severity::LOG_NOTICE, message)
49 }
50
51 fn info<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
52 self.format(w, Severity::LOG_INFO, message)
53 }
54
55 fn debug<W: Write>(&mut self, w: &mut W, message: T) -> Result<()> {
56 self.format(w, Severity::LOG_DEBUG, message)
57 }
58}
59
60#[derive(Clone, Debug)]
61pub struct Formatter3164 {
62 pub facility: Facility,
63 pub hostname: Option<String>,
64 pub process: String,
65 pub pid: u32,
66}
67
68impl<T: Display> LogFormat<T> for Formatter3164 {
69 fn format<W: Write>(&self, w: &mut W, severity: Severity, message: T) -> Result<()> {
70 let format =
71 time::format_description::parse("[month repr:short] [day] [hour]:[minute]:[second]")
72 .unwrap();
73
74 if let Some(ref hostname) = self.hostname {
75 write!(
76 w,
77 "<{}>{} {} {}[{}]: {}",
78 encode_priority(severity, self.facility),
79 now_local()
80 .map(|timestamp| timestamp.format(&format).unwrap())
81 .unwrap(),
82 hostname,
83 self.process,
84 self.pid,
85 message
86 )
87 .map_err(Error::Write)
88 } else {
89 write!(
90 w,
91 "<{}>{} {}[{}]: {}",
92 encode_priority(severity, self.facility),
93 now_local()
94 .map(|timestamp| timestamp.format(&format).unwrap())
95 .unwrap(),
96 self.process,
97 self.pid,
98 message
99 )
100 .map_err(Error::Write)
101 }
102 }
103}
104
105impl Default for Formatter3164 {
106 fn default() -> Self {
120 let (process, pid) = get_process_info().unwrap_or((String::new(), std::process::id()));
121 let hostname = get_hostname().ok();
122
123 Self {
124 facility: Default::default(),
125 hostname,
126 process,
127 pid,
128 }
129 }
130}
131
132pub type StructuredData = BTreeMap<String, BTreeMap<String, String>>;
134
135#[derive(Clone, Debug)]
136pub struct Formatter5424 {
137 pub facility: Facility,
138 pub hostname: Option<String>,
139 pub process: String,
140 pub pid: u32,
141}
142
143impl Formatter5424 {
144 pub fn format_5424_structured_data(&self, data: StructuredData) -> String {
145 if data.is_empty() {
146 "-".to_string()
147 } else {
148 let mut res = String::new();
149 for (id, params) in &data {
150 res = res + "[" + id;
151 for (name, value) in params {
152 res =
153 res + " " + name + "=\"" + &escape_structure_data_param_value(value) + "\"";
154 }
155 res += "]";
156 }
157
158 res
159 }
160 }
161}
162
163impl<T: Display> LogFormat<(u32, StructuredData, T)> for Formatter5424 {
164 fn format<W: Write>(
165 &self,
166 w: &mut W,
167 severity: Severity,
168 log_message: (u32, StructuredData, T),
169 ) -> Result<()> {
170 let (message_id, data, message) = log_message;
171
172 let timestamp = time::OffsetDateTime::now_utc();
174 let timestamp = timestamp
176 .replace_nanosecond(timestamp.nanosecond() / 1000 * 1000)
178 .unwrap();
179
180 write!(
181 w,
182 "<{}>1 {} {} {} {} {} {} {}", encode_priority(severity, self.facility),
184 timestamp
185 .format(&time::format_description::well_known::Rfc3339)
186 .unwrap(),
187 self.hostname
188 .as_ref()
189 .map(|x| &x[..])
190 .unwrap_or("localhost"),
191 self.process,
192 self.pid,
193 message_id,
194 self.format_5424_structured_data(data),
195 message
196 )
197 .map_err(Error::Write)
198 }
199}
200
201impl Default for Formatter5424 {
202 fn default() -> Self {
216 let Formatter3164 {
218 facility,
219 hostname,
220 process,
221 pid,
222 } = Default::default();
223 Self {
224 facility,
225 hostname,
226 process,
227 pid,
228 }
229 }
230}
231
232fn escape_structure_data_param_value(value: &str) -> String {
233 value
234 .replace('\\', "\\\\")
235 .replace('"', "\\\"")
236 .replace(']', "\\]")
237}
238
239fn encode_priority(severity: Severity, facility: Facility) -> Priority {
240 facility as u8 | severity as u8
241}
242
243#[cfg(unix)]
244fn now_local() -> std::result::Result<time::OffsetDateTime, time::error::IndeterminateOffset> {
247 Ok(time::OffsetDateTime::now_utc())
248}
249
250#[cfg(not(unix))]
251fn now_local() -> std::result::Result<time::OffsetDateTime, time::error::IndeterminateOffset> {
252 time::OffsetDateTime::now_local()
253}
254
255#[cfg(test)]
256mod test {
257 use super::*;
258
259 #[test]
260 fn backslash_is_escaped() {
261 let string = "\\";
262 let value = escape_structure_data_param_value(string);
263 assert_eq!(value, "\\\\");
264 }
265 #[test]
266 fn quote_is_escaped() {
267 let string = "foo\"bar";
268 let value = escape_structure_data_param_value(string);
269 assert_eq!(value, "foo\\\"bar");
270 }
271 #[test]
272 fn end_bracket_is_escaped() {
273 let string = "]";
274 let value = escape_structure_data_param_value(string);
275 assert_eq!(value, "\\]");
276 }
277
278 #[test]
279 fn test_formatter3164_defaults() {
280 let d = Formatter3164::default();
281
282 assert!(match d.facility {
284 Facility::LOG_USER => true,
285 _ => false,
286 });
287
288 assert!(match &d.hostname {
289 Some(hostname) => !hostname.is_empty(),
290 None => false,
291 });
292
293 assert!(!d.process.is_empty());
294
295 }
297
298 #[test]
299 fn test_formatter5424_defaults() {
300 let d = Formatter5424::default();
301
302 assert!(match d.facility {
304 Facility::LOG_USER => true,
305 _ => false,
306 });
307
308 assert!(match &d.hostname {
309 Some(hostname) => !hostname.is_empty(),
310 None => false,
311 });
312
313 assert!(!d.process.is_empty());
314
315 }
317}