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