sysly/
lib.rs

1#![deny(missing_docs)]
2//#![cfg_attr(test, deny(warnings))]
3#![cfg_attr(all(test, feature = "nightly"), feature(test))]
4
5//! sysly is a rust interface for [syslog](https://tools.ietf.org/html/rfc5424)
6
7//  #[cfg(all(test, feature = "nightly"))]
8//  extern crate test;
9extern crate time;
10extern crate unix_socket;
11
12use std::convert::AsRef;
13use std::io::{ self, Write };
14use std::net::{ Ipv4Addr, UdpSocket, SocketAddr, SocketAddrV4 };
15use std::path::Path;
16use time::Tm;
17use unix_socket::UnixStream;
18
19static NIL: &'static str = "-";
20
21/// Syslog [Facilities](https://tools.ietf.org/html/rfc5424#page-10)
22#[derive(Copy,Clone)]
23pub enum Facility {
24  /// kernal facility
25  KERN     = 0,
26  /// user facility
27  USER     = 1 << 3,
28  /// user facility
29  MAIL     = 2 << 3,
30  /// daemon facility
31  DAEMON   = 3 << 3,
32  /// auth facility
33  AUTH     = 4 << 3,
34  /// syslog facility
35  SYSLOG   = 5 << 3,
36  /// lineptr facility
37  LINEPTR  = 6 << 3,
38  /// news facility
39  NEWS     = 7 << 3,
40  /// uucp facility
41  UUCP     = 8 << 3,
42  /// clock facility
43  CLOCK    = 9 << 3,
44  /// auth facility
45  AUTHPRIV = 10 << 3,
46  /// ftp facility
47  FTP      = 11 << 3,
48  /// Local0 facility
49  LOCAL0   = 16 << 3,
50  /// Local1 facility
51  LOCAL1   = 17 << 3,
52  /// Local2 facility
53  LOCAL2   = 18 << 3,
54  /// Local3 facility
55  LOCAL3   = 19 << 3,
56  /// Local4 facility
57  LOCAL4   = 20 << 3,
58  /// Local5 facility
59  LOCAL5   = 21 << 3,
60  /// Local6 facility
61  LOCAL6   = 22 << 3,
62  /// Local7 facility
63  LOCAL7   = 23 << 3
64}
65
66/// Syslog [Severities](https://tools.ietf.org/html/rfc5424#page-11)
67pub enum Severity {
68  /// Emergency Severity
69  EMERGENCY,
70  /// Alert Severity
71  ALERT,
72  /// Critical Severity
73  CRITICAL,
74  /// Error Severity
75  ERROR,
76  /// Warning Severity
77  WARNING,
78  /// Notice Severity
79  NOTICE,
80  /// Info Severity
81  INFO,
82  /// Debug Severity
83  DEBUG
84}
85
86/// Result of log operations
87pub type Result = io::Result<()>;
88
89trait Transport {
90  fn send(&mut self, line: &str) -> Result;
91}
92
93impl Transport for (UdpSocket, SocketAddr) {
94  fn send(&mut self, line: &str) -> Result {
95    self.0.send_to(line.as_bytes(), &self.1).map(|_| ())
96  }
97}
98
99impl Transport for UnixStream {
100  fn send(&mut self, line: &str) -> Result {
101    self.write_all(line.as_bytes())
102  }
103}
104
105/// A rust interface for Syslog, a standard unix system logging service
106pub struct Syslog {
107  /// A Syslog facility to target when logging
108  facility: Facility,
109  /// A Syslog host entry as defined by
110  /// [rfc5424#section-6.2.4](https://tools.ietf.org/html/rfc5424#section-6.2.4)
111  host: Option<String>,
112  /// An optional app-name appended to Syslog messages as defined by 
113  /// [rfc5424#section-6.2.5](https://tools.ietf.org/html/rfc5424#section-6.2.5)
114  app: Option<String>,
115  /// An optional proc-id appended to Syslog messages as defined by
116  /// [rfc5424#section-6.2.6](https://tools.ietf.org/html/rfc5424#section-6.2.6)
117  pid: Option<String>,
118  /// An optional msg-id appended to Syslog messages as defined by 
119  /// [rfc5424#section-6.2.7](https://tools.ietf.org/html/rfc5424#section-6.2.7)
120  msgid: Option<String>,
121  transport: Box<Transport>
122}
123
124impl Syslog {
125   /// Factory for a Syslog appender that writes to
126   /// remote Syslog daemon listening a SocketAddr
127   pub fn udp(host: SocketAddr) -> Syslog {
128     let socket =
129       match UdpSocket::bind("0.0.0.0:0") {
130         Err(e) => panic!("error binding to local addr {}", e),
131         Ok(s) => s
132       };
133      let tup = (socket, host);
134      Syslog {
135        facility: Facility::USER,
136        host: None,
137        app: None,
138        pid: None,
139        msgid: None,
140        transport: Box::new(tup)
141      }
142  }
143
144  /// Same as udp with providing local loopback address with the standard syslog port
145  pub fn localudp() -> Syslog {
146    Syslog::udp(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127,0,0,1), 514)))
147  }
148
149  /// Factory for a Syslog appender that writes
150  /// to a host-local Syslog daemon listening on a unix socket domain
151  /// hosted at the given Path
152  pub fn unix<P: AsRef<Path>>(path: P) -> Syslog {
153    let stream =
154      match UnixStream::connect(path) {
155        Err(_) => panic!("failed to connect to socket"),
156        Ok(s)  => s
157      };
158    Syslog {
159      facility: Facility::USER,
160      host: None,
161      app: None,
162      pid: None,
163      msgid: None,
164      transport: Box::new(stream)
165    }
166  }
167  /// Returns a new Syslog appender configured to append with
168  /// the provided Facility
169  pub fn facility(self, facility: Facility) -> Syslog {
170    Syslog {
171      facility: facility,
172      host: self.host,
173      app: self.app,
174      pid: self.pid,
175      msgid: self.msgid,
176      transport: self.transport
177    }
178  }
179
180  /// Returns a new Syslog appender configured to append with
181  /// the provided host addr
182  pub fn host(self, local: &str) -> Syslog {
183    Syslog {
184      facility: self.facility,
185      host: Some(local.to_string()),
186      app: self.app,
187      pid: self.pid,
188      msgid: self.msgid,
189      transport: self.transport
190    }
191  }
192
193  /// Returns a new Syslog appender, configured to append with
194  /// the provided app-name
195  pub fn app(self, app: &str) -> Syslog {
196    Syslog {
197      facility: self.facility,
198      host: self.host,
199      app: Some(app.to_string()),
200      pid: self.pid,
201      msgid: self.msgid,
202      transport: self.transport
203    }
204  }
205
206  /// Returns a new Syslog appender configured to append with
207  /// the provided p(rocess)id
208  pub fn pid(self, pid: &str) -> Syslog {
209    Syslog {
210      facility: self.facility,
211      host: self.host,
212      app: self.app,
213      pid: Some(pid.to_string()),
214      msgid: self.msgid,
215      transport: self.transport
216    }
217  }
218
219  /// Returns a new Syslog appender configured to append with
220  /// the provided msgid
221  pub fn msgid(self, id: &str) -> Syslog {
222    Syslog {
223      facility: self.facility,
224      host: self.host,
225      app: self.app,
226      pid: self.pid,
227      msgid: Some(id.to_string()),
228      transport: self.transport
229    }
230  }
231
232  /// Emits a debug level message
233  pub fn debug(&mut self, msg: &str) -> Result {
234    self.log(Severity::DEBUG, msg)
235  }
236
237  /// Emits an info level message
238  pub fn info(&mut self, msg: &str) -> Result {
239    self.log(Severity::INFO, msg)
240  }
241
242  /// Emits an info level message
243  pub fn notice(&mut self, msg: &str) -> Result {
244    self.log(Severity::NOTICE, msg)
245  }
246
247  /// Emits an warn level message
248  pub fn warn(&mut self, msg: &str) -> Result {
249    self.log(Severity::WARNING, msg)
250  }
251
252  /// Emits an error level message
253  pub fn err(&mut self, msg: &str) -> Result {
254    self.log(Severity::ERROR, msg)
255  }
256
257  /// Emits a critical level message
258  pub fn critical(&mut self, msg: &str) -> Result {
259    self.log(Severity::CRITICAL, msg)
260  }
261
262  /// Emits an alert level message
263  pub fn alert(&mut self, msg: &str) -> Result {
264    self.log(Severity::ALERT, msg)
265  }
266
267  /// Emits a emergencycritical level message
268  pub fn emergency(&mut self, msg: &str) -> Result {
269    self.log(Severity::EMERGENCY, msg)
270  }
271
272  fn log(&mut self, severity: Severity,  msg: &str) -> Result {
273    let formatted = Syslog::line(
274        self.facility.clone(), severity, time::now(), self.host.clone(), self.app.clone(), self.pid.clone(), self.msgid.clone(), msg);
275    self.transport.send(&formatted)
276  }
277
278  fn line(facility: Facility, severity: Severity, timestamp: Tm, host: Option<String>, app: Option<String>, pid: Option<String>, msgid: Option<String>, msg: &str) -> String {
279    format!(
280      "<{:?}>1 {} {} {} {} {} {}",
281        Syslog::priority(facility, severity),
282        timestamp.rfc3339(),
283        host.unwrap_or(NIL.to_string()),
284        app.unwrap_or(NIL.to_string()),
285        pid.unwrap_or(NIL.to_string()),
286        msgid.unwrap_or(NIL.to_string()),
287        msg)
288  }
289
290  // computes the priority of a message based on a facility and severity
291  fn priority(facility: Facility, severity: Severity) -> u8 {
292    facility as u8 | severity as u8
293  }
294}
295
296#[cfg(test)]
297mod tests {
298  use super::{Syslog, Facility, Severity};
299  use time;
300  //use test::Bencher;
301
302  #[test]
303  fn test_syslog_line_defaults() {
304    let ts = time::now();
305    assert_eq!(Syslog::line(
306      Facility::LOCAL0, Severity::INFO, ts, None, None, None, None, "yo"),
307      format!("<134>1 {} - - - - yo", ts.rfc3339()));
308  }
309
310  #[test]
311  fn test_syslog_line_host() {
312    let ts = time::now();
313    let host = "foo.local";
314    assert_eq!(Syslog::line(
315      Facility::LOCAL0, Severity::INFO, ts, Some(host.to_string()), None, None, None, "yo"),
316      format!("<134>1 {} {} - - - yo", ts.rfc3339(), host));
317  }
318
319  #[test]
320  fn test_syslog_line_app() {
321    let ts = time::now();
322    let app = "sysly";
323    assert_eq!(Syslog::line(
324      Facility::LOCAL0, Severity::INFO, ts, None, Some(app.to_string()), None, None, "yo"),
325      format!("<134>1 {} - {} - - yo", ts.rfc3339(), app));
326  }
327
328  #[test]
329  fn test_syslog_line_pid() {
330    let ts = time::now();
331    let pid = "16";
332    assert_eq!(Syslog::line(
333      Facility::LOCAL0, Severity::INFO, ts, None, None, Some(pid.to_string()), None, "yo"),
334      format!("<134>1 {} - - {} - yo", ts.rfc3339(), pid));
335  }
336
337  #[test]
338  fn test_syslog_line_msgid() {
339    let ts = time::now();
340    let msgid = "TCPIN";
341    assert_eq!(Syslog::line(
342      Facility::LOCAL0, Severity::INFO, ts, None, None, None, Some(msgid.to_string()), "yo"),
343      format!("<134>1 {} - - - {} yo", ts.rfc3339(), msgid));
344  }
345
346  //#[bench]
347  //fn bench_assembly_line(b: &mut Bencher) {
348  // b.iter(|| Syslog::line(
349  //    Facility::LOCAL0, Severity::INFO, time::now(), None, None, None, None, "yo"))
350  //}
351}