1#![deny(missing_docs)]
2#![cfg_attr(all(test, feature = "nightly"), feature(test))]
4
5extern 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#[derive(Copy,Clone)]
23pub enum Facility {
24 KERN = 0,
26 USER = 1 << 3,
28 MAIL = 2 << 3,
30 DAEMON = 3 << 3,
32 AUTH = 4 << 3,
34 SYSLOG = 5 << 3,
36 LINEPTR = 6 << 3,
38 NEWS = 7 << 3,
40 UUCP = 8 << 3,
42 CLOCK = 9 << 3,
44 AUTHPRIV = 10 << 3,
46 FTP = 11 << 3,
48 LOCAL0 = 16 << 3,
50 LOCAL1 = 17 << 3,
52 LOCAL2 = 18 << 3,
54 LOCAL3 = 19 << 3,
56 LOCAL4 = 20 << 3,
58 LOCAL5 = 21 << 3,
60 LOCAL6 = 22 << 3,
62 LOCAL7 = 23 << 3
64}
65
66pub enum Severity {
68 EMERGENCY,
70 ALERT,
72 CRITICAL,
74 ERROR,
76 WARNING,
78 NOTICE,
80 INFO,
82 DEBUG
84}
85
86pub 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
105pub struct Syslog {
107 facility: Facility,
109 host: Option<String>,
112 app: Option<String>,
115 pid: Option<String>,
118 msgid: Option<String>,
121 transport: Box<Transport>
122}
123
124impl Syslog {
125 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 pub fn localudp() -> Syslog {
146 Syslog::udp(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127,0,0,1), 514)))
147 }
148
149 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 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 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 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 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 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 pub fn debug(&mut self, msg: &str) -> Result {
234 self.log(Severity::DEBUG, msg)
235 }
236
237 pub fn info(&mut self, msg: &str) -> Result {
239 self.log(Severity::INFO, msg)
240 }
241
242 pub fn notice(&mut self, msg: &str) -> Result {
244 self.log(Severity::NOTICE, msg)
245 }
246
247 pub fn warn(&mut self, msg: &str) -> Result {
249 self.log(Severity::WARNING, msg)
250 }
251
252 pub fn err(&mut self, msg: &str) -> Result {
254 self.log(Severity::ERROR, msg)
255 }
256
257 pub fn critical(&mut self, msg: &str) -> Result {
259 self.log(Severity::CRITICAL, msg)
260 }
261
262 pub fn alert(&mut self, msg: &str) -> Result {
264 self.log(Severity::ALERT, msg)
265 }
266
267 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 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 #[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 }