1use std::fmt::Display;
2use std::io::Write;
3use time::{self, OffsetDateTime};
4
5use crate::errors::*;
6use crate::facility::Facility;
7use crate::get_hostname;
8use crate::get_process_info;
9use crate::Priority;
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 self.format_at(w, severity, now_local().unwrap(), message)
27 }
28
29 fn format_at<W: Write>(
30 &self,
31 w: &mut W,
32 severity: Severity,
33 time: OffsetDateTime,
34 message: &T,
35 ) -> Result<()>;
36
37 fn emerg<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
38 self.format(w, Severity::LOG_EMERG, message)
39 }
40
41 fn alert<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
42 self.format(w, Severity::LOG_ALERT, message)
43 }
44
45 fn crit<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
46 self.format(w, Severity::LOG_CRIT, message)
47 }
48
49 fn err<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
50 self.format(w, Severity::LOG_ERR, message)
51 }
52
53 fn warning<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
54 self.format(w, Severity::LOG_WARNING, message)
55 }
56
57 fn notice<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
58 self.format(w, Severity::LOG_NOTICE, message)
59 }
60
61 fn info<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
62 self.format(w, Severity::LOG_INFO, message)
63 }
64
65 fn debug<W: Write>(&mut self, w: &mut W, message: &T) -> Result<()> {
66 self.format(w, Severity::LOG_DEBUG, message)
67 }
68}
69
70#[derive(Clone, Debug)]
71pub struct Formatter3164 {
72 pub facility: Facility,
73 pub hostname: Option<String>,
74 pub process: String,
75 pub pid: u32,
76}
77
78impl<T: Display> LogFormat<T> for Formatter3164 {
79 fn format_at<W: Write>(
80 &self,
81 w: &mut W,
82 severity: Severity,
83 time: OffsetDateTime,
84 message: &T,
85 ) -> Result<()> {
86 let format =
87 time::format_description::parse("[month repr:short] [day] [hour]:[minute]:[second]")
88 .unwrap();
89
90 if let Some(ref hostname) = self.hostname {
91 writeln!(
92 w,
93 "<{}>{} {} {}[{}]: {}",
94 encode_priority(severity, self.facility),
95 time.format(&format).unwrap(),
96 hostname,
97 self.process,
98 self.pid,
99 message
100 )
101 .chain_err(|| ErrorKind::Format)
102 } else {
103 writeln!(
104 w,
105 "<{}>{} {}[{}]: {}",
106 encode_priority(severity, self.facility),
107 time.format(&format).unwrap(),
108 self.process,
109 self.pid,
110 message
111 )
112 .chain_err(|| ErrorKind::Format)
113 }
114 }
115}
116
117impl Default for Formatter3164 {
118 fn default() -> Self {
132 let (process, pid) = get_process_info().unwrap_or((String::new(), std::process::id()));
133 let hostname = get_hostname().ok();
134
135 Self {
136 facility: Default::default(),
137 hostname,
138 process,
139 pid,
140 }
141 }
142}
143
144pub type StructuredData = Vec<(String, Vec<(String, String)>)>;
146
147pub struct SyslogMessage {
148 pub message_level: u32,
149 pub structured: StructuredData,
150 pub message: String,
151}
152
153#[derive(Clone, Debug)]
154pub struct Formatter5424 {
155 pub facility: Facility,
156 pub hostname: Option<String>,
157 pub process: String,
158 pub pid: u32,
159}
160
161impl Formatter5424 {
162 pub fn format_5424_structured_data(&self, data: &StructuredData) -> String {
163 if data.is_empty() {
164 "-".to_string()
165 } else {
166 let mut res = String::new();
167 for (id, params) in data {
168 res = res + "[" + id;
169 for (name, value) in params {
170 res = res
171 + " "
172 + name
173 + "=\""
174 + &escape_structure_data_param_value(&value)
175 + "\"";
176 }
177 res += "]";
178 }
179
180 res
181 }
182 }
183}
184
185impl LogFormat<SyslogMessage> for Formatter5424 {
186 fn format_at<W: Write>(
187 &self,
188 w: &mut W,
189 severity: Severity,
190 timestamp: OffsetDateTime,
191 message: &SyslogMessage,
192 ) -> Result<()> {
193 let timestamp = timestamp
195 .replace_nanosecond(timestamp.nanosecond() / 1000 * 1000)
197 .unwrap();
198
199 write!(
200 w,
201 "<{}>1 {} {} {} {} {} {} {}{}", encode_priority(severity, self.facility),
203 timestamp
204 .format(&time::format_description::well_known::Rfc3339)
205 .expect("Can format time"),
206 self.hostname
207 .as_ref()
208 .map(|x| &x[..])
209 .unwrap_or("localhost"),
210 self.process,
211 self.pid,
212 message.message_level,
213 self.format_5424_structured_data(&message.structured),
214 message.message,
215 if message.message.ends_with('\n') {
217 ""
218 } else {
219 "\n"
220 },
221 )
222 .chain_err(|| "Failed to write syslog message")
223 }
224}
225
226impl Default for Formatter5424 {
227 fn default() -> Self {
241 let Formatter3164 {
243 facility,
244 hostname,
245 process,
246 pid,
247 } = Default::default();
248 Self {
249 facility,
250 hostname,
251 process,
252 pid,
253 }
254 }
255}
256
257fn escape_structure_data_param_value(value: &str) -> String {
258 value
259 .replace('\\', "\\\\")
260 .replace('"', "\\\"")
261 .replace(']', "\\]")
262}
263
264fn encode_priority(severity: Severity, facility: Facility) -> Priority {
265 facility as u8 | severity as u8
266}
267
268#[cfg(unix)]
269fn now_local() -> std::result::Result<time::OffsetDateTime, time::error::IndeterminateOffset> {
272 Ok(time::OffsetDateTime::now_utc())
273}
274
275#[cfg(not(unix))]
276fn now_local() -> std::result::Result<time::OffsetDateTime, time::error::IndeterminateOffset> {
277 time::OffsetDateTime::now_local()
278}
279
280#[cfg(test)]
281mod test {
282 use super::*;
283
284 #[test]
285 fn backslash_is_escaped() {
286 let string = "\\";
287 let value = escape_structure_data_param_value(string);
288 assert_eq!(value, "\\\\");
289 }
290 #[test]
291 fn quote_is_escaped() {
292 let string = "foo\"bar";
293 let value = escape_structure_data_param_value(string);
294 assert_eq!(value, "foo\\\"bar");
295 }
296 #[test]
297 fn end_bracket_is_escaped() {
298 let string = "]";
299 let value = escape_structure_data_param_value(string);
300 assert_eq!(value, "\\]");
301 }
302
303 #[test]
304 fn test_formatter3164_defaults() {
305 let d = Formatter3164::default();
306
307 assert!(match d.facility {
309 Facility::LOG_USER => true,
310 _ => false,
311 });
312
313 assert!(match &d.hostname {
314 Some(hostname) => !hostname.is_empty(),
315 None => false,
316 });
317
318 assert!(!d.process.is_empty());
319
320 }
322
323 #[test]
324 fn test_formatter5424_defaults() {
325 let d = Formatter5424::default();
326
327 assert!(match d.facility {
329 Facility::LOG_USER => true,
330 _ => false,
331 });
332
333 assert!(match &d.hostname {
334 Some(hostname) => !hostname.is_empty(),
335 None => false,
336 });
337
338 assert!(!d.process.is_empty());
339
340 }
342}