syslog/
lib.rs

1//! Syslog
2//!
3//! This crate provides facilities to send log messages via syslog.
4//! It supports Unix sockets for local syslog, UDP and TCP for remote servers.
5//!
6//! Messages can be passed directly without modification, or in RFC 3164 or RFC 5424 format
7//!
8//! The code is available on [Github](https://github.com/Geal/rust-syslog)
9//!
10//! # Example
11//!
12//! ```rust
13//! use syslog::{Facility, Formatter3164};
14//!
15//! let formatter = Formatter3164 {
16//!     facility: Facility::LOG_USER,
17//!     hostname: None,
18//!     process: "myprogram".into(),
19//!     pid: 0,
20//! };
21//!
22//! match syslog::unix(formatter) {
23//!     Err(e) => println!("impossible to connect to syslog: {:?}", e),
24//!     Ok(mut writer) => {
25//!         writer.err("hello world").expect("could not write error message");
26//!     }
27//! }
28//! ```
29//!
30//! It can be used directly with the log crate as follows:
31//!
32//! ```rust
33//! extern crate log;
34//!
35//! use syslog::{Facility, Formatter3164, BasicLogger};
36//! use log::{SetLoggerError, LevelFilter, info};
37//!
38//! let formatter = Formatter3164 {
39//!     facility: Facility::LOG_USER,
40//!     hostname: None,
41//!     process: "myprogram".into(),
42//!     pid: 0,
43//! };
44//!
45//! let logger = match syslog::unix(formatter) {
46//!     Err(e) => { println!("impossible to connect to syslog: {:?}", e); return; },
47//!     Ok(logger) => logger,
48//! };
49//! log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
50//!         .map(|()| log::set_max_level(LevelFilter::Info));
51//!
52//! info!("hello world");
53//! ```
54extern crate log;
55extern crate time;
56
57use std::env;
58use std::fmt::{self, Arguments};
59use std::io::{self, BufWriter, Write};
60use std::net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket};
61#[cfg(unix)]
62use std::os::unix::net::{UnixDatagram, UnixStream};
63use std::path::Path;
64use std::process;
65use std::sync::{Arc, Mutex};
66
67use log::{Level, Log, Metadata, Record};
68
69mod errors;
70mod facility;
71mod format;
72#[cfg(test)]
73mod tests;
74
75pub use errors::*;
76pub use facility::Facility;
77pub use format::Severity;
78pub use format::{Formatter3164, Formatter5424, LogFormat};
79
80pub type Priority = u8;
81
82const UNIX_SOCK_PATHS: [&str; 3] = ["/dev/log", "/var/run/syslog", "/var/run/log"];
83
84/// Main logging structure
85pub struct Logger<Backend: Write, Formatter> {
86    pub formatter: Formatter,
87    pub backend: Backend,
88}
89
90impl<W: Write, F> Logger<W, F> {
91    pub fn new(backend: W, formatter: F) -> Self {
92        Logger { backend, formatter }
93    }
94
95    pub fn emerg<T>(&mut self, message: T) -> Result<()>
96    where
97        F: LogFormat<T>,
98    {
99        self.formatter.emerg(&mut self.backend, message)
100    }
101
102    pub fn alert<T>(&mut self, message: T) -> Result<()>
103    where
104        F: LogFormat<T>,
105    {
106        self.formatter.alert(&mut self.backend, message)
107    }
108
109    pub fn crit<T>(&mut self, message: T) -> Result<()>
110    where
111        F: LogFormat<T>,
112    {
113        self.formatter.crit(&mut self.backend, message)
114    }
115
116    pub fn err<T>(&mut self, message: T) -> Result<()>
117    where
118        F: LogFormat<T>,
119    {
120        self.formatter.err(&mut self.backend, message)
121    }
122
123    pub fn warning<T>(&mut self, message: T) -> Result<()>
124    where
125        F: LogFormat<T>,
126    {
127        self.formatter.warning(&mut self.backend, message)
128    }
129
130    pub fn notice<T>(&mut self, message: T) -> Result<()>
131    where
132        F: LogFormat<T>,
133    {
134        self.formatter.notice(&mut self.backend, message)
135    }
136
137    pub fn info<T>(&mut self, message: T) -> Result<()>
138    where
139        F: LogFormat<T>,
140    {
141        self.formatter.info(&mut self.backend, message)
142    }
143
144    pub fn debug<T>(&mut self, message: T) -> Result<()>
145    where
146        F: LogFormat<T>,
147    {
148        self.formatter.debug(&mut self.backend, message)
149    }
150}
151
152pub enum LoggerBackend {
153    /// Unix socket, temp file path, log file path
154    #[cfg(unix)]
155    Unix(UnixDatagram),
156    #[cfg(not(unix))]
157    Unix(()),
158    #[cfg(unix)]
159    UnixStream(BufWriter<UnixStream>),
160    #[cfg(not(unix))]
161    UnixStream(()),
162    Udp(UdpSocket, SocketAddr),
163    Tcp(BufWriter<TcpStream>),
164}
165
166impl Write for LoggerBackend {
167    /// Sends a message directly, without any formatting
168    fn write(&mut self, message: &[u8]) -> io::Result<usize> {
169        match *self {
170            #[cfg(unix)]
171            LoggerBackend::Unix(ref dgram) => dgram.send(message),
172            #[cfg(unix)]
173            LoggerBackend::UnixStream(ref mut socket) => {
174                let null = [0; 1];
175                socket
176                    .write(message)
177                    .and_then(|sz| socket.write(&null).map(|_| sz))
178                    .and_then(|sz| socket.flush().map(|_| sz))
179            }
180            LoggerBackend::Udp(ref socket, ref addr) => socket.send_to(message, addr),
181            LoggerBackend::Tcp(ref mut socket) => socket
182                .write(message)
183                .and_then(|sz| socket.flush().map(|_| sz)),
184            #[cfg(not(unix))]
185            LoggerBackend::Unix(_) | LoggerBackend::UnixStream(_) => {
186                Err(io::Error::new(io::ErrorKind::Other, "unsupported platform"))
187            }
188        }
189    }
190
191    fn write_fmt(&mut self, args: Arguments) -> io::Result<()> {
192        match *self {
193            #[cfg(unix)]
194            LoggerBackend::Unix(ref dgram) => {
195                let message = fmt::format(args);
196                dgram.send(message.as_bytes()).map(|_| ())
197            }
198            #[cfg(unix)]
199            LoggerBackend::UnixStream(ref mut socket) => {
200                let null = [0; 1];
201                socket
202                    .write_fmt(args)
203                    .and_then(|_| socket.write(&null).map(|_| ()))
204                    .and_then(|sz| socket.flush().map(|_| sz))
205            }
206            LoggerBackend::Udp(ref socket, ref addr) => {
207                let message = fmt::format(args);
208                socket.send_to(message.as_bytes(), addr).map(|_| ())
209            }
210            LoggerBackend::Tcp(ref mut socket) => socket
211                .write_fmt(args)
212                .and_then(|sz| socket.flush().map(|_| sz)),
213            #[cfg(not(unix))]
214            LoggerBackend::Unix(_) | LoggerBackend::UnixStream(_) => {
215                Err(io::Error::new(io::ErrorKind::Other, "unsupported platform"))
216            }
217        }
218    }
219
220    fn flush(&mut self) -> io::Result<()> {
221        match *self {
222            #[cfg(unix)]
223            LoggerBackend::Unix(_) => Ok(()),
224            #[cfg(unix)]
225            LoggerBackend::UnixStream(ref mut socket) => socket.flush(),
226            LoggerBackend::Udp(_, _) => Ok(()),
227            LoggerBackend::Tcp(ref mut socket) => socket.flush(),
228            #[cfg(not(unix))]
229            LoggerBackend::Unix(_) | LoggerBackend::UnixStream(_) => {
230                Err(io::Error::new(io::ErrorKind::Other, "unsupported platform"))
231            }
232        }
233    }
234}
235
236/// Returns a Logger using unix socket to target local syslog ( using /dev/log or /var/run/syslog)
237#[cfg(unix)]
238pub fn unix<F: Clone>(formatter: F) -> Result<Logger<LoggerBackend, F>> {
239    UNIX_SOCK_PATHS
240        .iter()
241        .find_map(|path| {
242            unix_connect(formatter.clone(), *path).map_or_else(
243                |e| {
244                    if let Error::Io(ref io_err) = e {
245                        if io_err.kind() == io::ErrorKind::NotFound {
246                            None // not considered an error, try the next path
247                        } else {
248                            Some(Err(e))
249                        }
250                    } else {
251                        Some(Err(e))
252                    }
253                },
254                |logger| Some(Ok(logger)),
255            )
256        })
257        .transpose()
258        .map_err(|e| Error::Initialization(Box::new(e)))?
259        .ok_or_else(|| Error::Initialization("unix socket paths not found".into()))
260}
261
262#[cfg(not(unix))]
263pub fn unix<F: Clone>(_formatter: F) -> Result<Logger<LoggerBackend, F>> {
264    Err(ErrorKind::UnsupportedPlatform)?
265}
266
267/// Returns a Logger using unix socket to target local syslog at user provided path
268#[cfg(unix)]
269pub fn unix_custom<P: AsRef<Path>, F>(formatter: F, path: P) -> Result<Logger<LoggerBackend, F>> {
270    unix_connect(formatter, path).map_err(|e| Error::Initialization(Box::new(e)))
271}
272
273#[cfg(not(unix))]
274pub fn unix_custom<P: AsRef<Path>, F>(_formatter: F, _path: P) -> Result<Logger<LoggerBackend, F>> {
275    Err(ErrorKind::UnsupportedPlatform)?
276}
277
278#[cfg(unix)]
279fn unix_connect<P: AsRef<Path>, F>(formatter: F, path: P) -> Result<Logger<LoggerBackend, F>> {
280    let sock = UnixDatagram::unbound()?;
281    match sock.connect(&path) {
282        Ok(()) => Ok(Logger {
283            formatter,
284            backend: LoggerBackend::Unix(sock),
285        }),
286        Err(ref e) if e.raw_os_error() == Some(libc::EPROTOTYPE) => {
287            let sock = UnixStream::connect(path)?;
288            Ok(Logger {
289                formatter,
290                backend: LoggerBackend::UnixStream(BufWriter::new(sock)),
291            })
292        }
293        Err(e) => Err(e.into()),
294    }
295}
296
297/// returns a UDP logger connecting `local` and `server`
298pub fn udp<T: ToSocketAddrs, U: ToSocketAddrs, F>(
299    formatter: F,
300    local: T,
301    server: U,
302) -> Result<Logger<LoggerBackend, F>> {
303    server
304        .to_socket_addrs()
305        .map_err(|e| Error::Initialization(Box::new(e)))
306        .and_then(|mut server_addr_opt| {
307            server_addr_opt
308                .next()
309                .ok_or_else(|| Error::Initialization("no server address".into()))
310        })
311        .and_then(|server_addr| {
312            UdpSocket::bind(local)
313                .map_err(|e| Error::Initialization(Box::new(e)))
314                .map(|socket| Logger {
315                    formatter,
316                    backend: LoggerBackend::Udp(socket, server_addr),
317                })
318        })
319}
320
321/// returns a TCP logger connecting `local` and `server`
322pub fn tcp<T: ToSocketAddrs, F>(formatter: F, server: T) -> Result<Logger<LoggerBackend, F>> {
323    TcpStream::connect(server)
324        .map_err(|e| Error::Initialization(e.into()))
325        .map(|socket| Logger {
326            formatter,
327            backend: LoggerBackend::Tcp(BufWriter::new(socket)),
328        })
329}
330
331pub struct BasicLogger {
332    logger: Arc<Mutex<Logger<LoggerBackend, Formatter3164>>>,
333}
334
335impl BasicLogger {
336    pub fn new(logger: Logger<LoggerBackend, Formatter3164>) -> BasicLogger {
337        BasicLogger {
338            logger: Arc::new(Mutex::new(logger)),
339        }
340    }
341}
342
343#[allow(unused_variables, unused_must_use)]
344impl Log for BasicLogger {
345    fn enabled(&self, metadata: &Metadata) -> bool {
346        metadata.level() <= log::max_level() && metadata.level() <= log::STATIC_MAX_LEVEL
347    }
348
349    fn log(&self, record: &Record) {
350        if self.enabled(record.metadata()) {
351            //FIXME: temporary patch to compile
352            let message = format!("{}", record.args());
353            let mut logger = self.logger.lock().unwrap();
354            match record.level() {
355                Level::Error => logger.err(message),
356                Level::Warn => logger.warning(message),
357                Level::Info => logger.info(message),
358                Level::Debug => logger.debug(message),
359                Level::Trace => logger.debug(message),
360            };
361        }
362    }
363
364    fn flush(&self) {
365        let _ = self.logger.lock().unwrap().backend.flush();
366    }
367}
368
369/// Unix socket Logger init function compatible with log crate
370#[cfg(unix)]
371pub fn init_unix(facility: Facility, log_level: log::LevelFilter) -> Result<()> {
372    let (process, pid) = get_process_info()?;
373    let formatter = Formatter3164 {
374        facility,
375        hostname: None,
376        process,
377        pid,
378    };
379    unix(formatter).and_then(|logger| {
380        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
381            .map_err(|e| Error::Initialization(Box::new(e)))
382    })?;
383
384    log::set_max_level(log_level);
385    Ok(())
386}
387
388#[cfg(not(unix))]
389pub fn init_unix(_facility: Facility, _log_level: log::LevelFilter) -> Result<()> {
390    Err(ErrorKind::UnsupportedPlatform)?
391}
392
393/// Unix socket Logger init function compatible with log crate and user provided socket path
394#[cfg(unix)]
395pub fn init_unix_custom<P: AsRef<Path>>(
396    facility: Facility,
397    log_level: log::LevelFilter,
398    path: P,
399) -> Result<()> {
400    let (process, pid) = get_process_info()?;
401    let formatter = Formatter3164 {
402        facility,
403        hostname: None,
404        process,
405        pid,
406    };
407    unix_custom(formatter, path).and_then(|logger| {
408        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
409            .map_err(|e| Error::Initialization(Box::new(e)))
410    })?;
411
412    log::set_max_level(log_level);
413    Ok(())
414}
415
416#[cfg(not(unix))]
417pub fn init_unix_custom<P: AsRef<Path>>(
418    _facility: Facility,
419    _log_level: log::LevelFilter,
420    _path: P,
421) -> Result<()> {
422    Err(ErrorKind::UnsupportedPlatform)?
423}
424
425/// UDP Logger init function compatible with log crate
426pub fn init_udp<T: ToSocketAddrs>(
427    local: T,
428    server: T,
429    hostname: String,
430    facility: Facility,
431    log_level: log::LevelFilter,
432) -> Result<()> {
433    let (process, pid) = get_process_info()?;
434    let formatter = Formatter3164 {
435        facility,
436        hostname: Some(hostname),
437        process,
438        pid,
439    };
440    udp(formatter, local, server).and_then(|logger| {
441        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
442            .map_err(|e| Error::Initialization(Box::new(e)))
443    })?;
444
445    log::set_max_level(log_level);
446    Ok(())
447}
448
449/// TCP Logger init function compatible with log crate
450pub fn init_tcp<T: ToSocketAddrs>(
451    server: T,
452    hostname: String,
453    facility: Facility,
454    log_level: log::LevelFilter,
455) -> Result<()> {
456    let (process, pid) = get_process_info()?;
457    let formatter = Formatter3164 {
458        facility,
459        hostname: Some(hostname),
460        process,
461        pid,
462    };
463
464    tcp(formatter, server).and_then(|logger| {
465        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
466            .map_err(|e| Error::Initialization(Box::new(e)))
467    })?;
468
469    log::set_max_level(log_level);
470    Ok(())
471}
472
473/// Initializes logging subsystem for log crate
474///
475/// This tries to connect to syslog by following ways:
476///
477/// 1. Unix sockets /dev/log and /var/run/syslog (in this order)
478/// 2. Tcp connection to 127.0.0.1:601
479/// 3. Udp connection to 127.0.0.1:514
480///
481/// Note the last option usually (almost) never fails in this method. So
482/// this method doesn't return error even if there is no syslog.
483///
484/// If `application_name` is `None` name is derived from executable name
485pub fn init(
486    facility: Facility,
487    log_level: log::LevelFilter,
488    application_name: Option<&str>,
489) -> Result<()> {
490    let (process_name, pid) = get_process_info()?;
491    let process = application_name.map(From::from).unwrap_or(process_name);
492    let mut formatter = Formatter3164 {
493        facility,
494        hostname: None,
495        process,
496        pid,
497    };
498
499    let backend = if let Ok(logger) = unix(formatter.clone()) {
500        logger.backend
501    } else {
502        formatter.hostname = get_hostname().ok();
503        if let Ok(tcp_stream) = TcpStream::connect(("127.0.0.1", 601)) {
504            LoggerBackend::Tcp(BufWriter::new(tcp_stream))
505        } else {
506            let udp_addr = "127.0.0.1:514".parse().unwrap();
507            let udp_stream = UdpSocket::bind(("127.0.0.1", 0))?;
508            LoggerBackend::Udp(udp_stream, udp_addr)
509        }
510    };
511    log::set_boxed_logger(Box::new(BasicLogger::new(Logger { formatter, backend })))
512        .map_err(|e| Error::Initialization(Box::new(e)))?;
513
514    log::set_max_level(log_level);
515    Ok(())
516}
517
518fn get_process_info() -> Result<(String, u32)> {
519    env::current_exe()
520        .map_err(|e| Error::Initialization(Box::new(e)))
521        .and_then(|path| {
522            path.file_name()
523                .and_then(|os_name| os_name.to_str())
524                .map(|name| name.to_string())
525                .ok_or_else(|| Error::Initialization("process name not found".into()))
526        })
527        .map(|name| (name, process::id()))
528}
529
530fn get_hostname() -> Result<String> {
531    Ok(hostname::get()?.to_string_lossy().to_string())
532}