syslog_tls/
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_tls::{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_tls::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_tls::{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_tls::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//!
54//!
55#[macro_use]
56extern crate error_chain;
57
58use log::{Level, Log, Metadata, Record};
59use std::env;
60use std::fmt::{self, Arguments};
61use std::io::{self, BufWriter, Write};
62use std::net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket};
63#[cfg(unix)]
64use std::os::unix::net::{UnixDatagram, UnixStream};
65use std::path::Path;
66use std::process;
67use std::sync::{Arc, Mutex};
68
69mod errors;
70mod facility;
71mod format;
72pub use errors::*;
73pub use facility::Facility;
74pub use format::Severity;
75
76pub use format::{Formatter3164, Formatter5424, LogFormat, StructuredData, SyslogMessage};
77use native_tls::{Certificate, TlsConnector, TlsStream};
78use time::OffsetDateTime;
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 send<T>(&mut self, severity: Severity, message: &T) -> Result<()>
96    where
97        F: LogFormat<T>,
98    {
99        self.formatter.format(&mut self.backend, severity, message)
100    }
101
102    pub fn send_at<T>(
103        &mut self,
104        severity: Severity,
105        time: OffsetDateTime,
106        message: &T,
107    ) -> Result<()>
108    where
109        F: LogFormat<T>,
110    {
111        self.formatter
112            .format_at(&mut self.backend, severity, time, message)
113    }
114
115    pub fn emerg<T>(&mut self, message: T) -> Result<()>
116    where
117        F: LogFormat<T>,
118    {
119        self.formatter.emerg(&mut self.backend, &message)
120    }
121
122    pub fn alert<T>(&mut self, message: T) -> Result<()>
123    where
124        F: LogFormat<T>,
125    {
126        self.formatter.alert(&mut self.backend, &message)
127    }
128
129    pub fn crit<T>(&mut self, message: T) -> Result<()>
130    where
131        F: LogFormat<T>,
132    {
133        self.formatter.crit(&mut self.backend, &message)
134    }
135
136    pub fn err<T>(&mut self, message: T) -> Result<()>
137    where
138        F: LogFormat<T>,
139    {
140        self.formatter.err(&mut self.backend, &message)
141    }
142
143    pub fn warning<T>(&mut self, message: T) -> Result<()>
144    where
145        F: LogFormat<T>,
146    {
147        self.formatter.warning(&mut self.backend, &message)
148    }
149
150    pub fn notice<T>(&mut self, message: T) -> Result<()>
151    where
152        F: LogFormat<T>,
153    {
154        self.formatter.notice(&mut self.backend, &message)
155    }
156
157    pub fn info<T>(&mut self, message: T) -> Result<()>
158    where
159        F: LogFormat<T>,
160    {
161        self.formatter.info(&mut self.backend, &message)
162    }
163
164    pub fn debug<T>(&mut self, message: T) -> Result<()>
165    where
166        F: LogFormat<T>,
167    {
168        self.formatter.debug(&mut self.backend, &message)
169    }
170}
171
172pub enum LoggerBackend {
173    /// Unix socket, temp file path, log file path
174    #[cfg(unix)]
175    Unix(UnixDatagram),
176    #[cfg(not(unix))]
177    Unix(()),
178    #[cfg(unix)]
179    UnixStream(BufWriter<UnixStream>),
180    #[cfg(not(unix))]
181    UnixStream(()),
182    Udp(UdpSocket, SocketAddr),
183    Tcp(BufWriter<TcpStream>),
184    Tls(BufWriter<TlsStream<TcpStream>>),
185}
186
187impl Write for LoggerBackend {
188    /// Sends a message directly, without any formatting
189    fn write(&mut self, message: &[u8]) -> io::Result<usize> {
190        match *self {
191            #[cfg(unix)]
192            LoggerBackend::Unix(ref dgram) => dgram.send(message),
193            #[cfg(unix)]
194            LoggerBackend::UnixStream(ref mut socket) => {
195                let null = [0; 1];
196                socket
197                    .write(message)
198                    .and_then(|sz| socket.write(&null).map(|_| sz))
199            }
200            LoggerBackend::Udp(ref socket, ref addr) => socket.send_to(message, addr),
201            LoggerBackend::Tcp(ref mut socket) => socket.write(message).and_then(|bytes_written| {
202                socket.flush()?;
203                Ok(bytes_written)
204            }),
205            LoggerBackend::Tls(ref mut socket) => socket.write(message).and_then(|bytes_written| {
206                socket.flush()?;
207                Ok(bytes_written)
208            }),
209            #[cfg(not(unix))]
210            LoggerBackend::Unix(_) | LoggerBackend::UnixStream(_) => {
211                Err(io::Error::new(io::ErrorKind::Other, "unsupported platform"))
212            }
213        }
214    }
215
216    fn write_fmt(&mut self, args: Arguments) -> io::Result<()> {
217        // Ensure we send the entire message was one packet, because each write may do a flush.
218        let message = fmt::format(args);
219        self.write(message.as_bytes()).map(|_| ())
220    }
221
222    fn flush(&mut self) -> io::Result<()> {
223        match *self {
224            #[cfg(unix)]
225            LoggerBackend::Unix(_) => Ok(()),
226            #[cfg(unix)]
227            LoggerBackend::UnixStream(ref mut socket) => socket.flush(),
228            LoggerBackend::Udp(_, _) => Ok(()),
229            LoggerBackend::Tcp(ref mut socket) => socket.flush(),
230            LoggerBackend::Tls(ref mut socket) => socket.flush(),
231            #[cfg(not(unix))]
232            LoggerBackend::Unix(_) | LoggerBackend::UnixStream(_) => {
233                Err(io::Error::new(io::ErrorKind::Other, "unsupported platform"))
234            }
235        }
236    }
237}
238
239/// Returns a Logger using unix socket to target local syslog ( using /dev/log or /var/run/syslog)
240#[cfg(unix)]
241pub fn unix<F: Clone>(formatter: F) -> Result<Logger<LoggerBackend, F>> {
242    UNIX_SOCK_PATHS
243        .iter()
244        .find_map(|path| {
245            unix_connect(formatter.clone(), *path).map_or_else(
246                |e| {
247                    if let ErrorKind::Io(ref io_err) = e.kind() {
248                        if io_err.kind() == io::ErrorKind::NotFound {
249                            None // not considered an error, try the next path
250                        } else {
251                            Some(Err(e))
252                        }
253                    } else {
254                        Some(Err(e))
255                    }
256                },
257                |logger| Some(Ok(logger)),
258            )
259        })
260        .transpose()
261        .chain_err(|| ErrorKind::Initialization)?
262        .ok_or_else(|| Error::from_kind(ErrorKind::Initialization))
263}
264
265#[cfg(not(unix))]
266pub fn unix<F: Clone>(_formatter: F) -> Result<Logger<LoggerBackend, F>> {
267    Err(ErrorKind::UnsupportedPlatform)?
268}
269
270/// Returns a Logger using unix socket to target local syslog at user provided path
271#[cfg(unix)]
272pub fn unix_custom<P: AsRef<Path>, F>(formatter: F, path: P) -> Result<Logger<LoggerBackend, F>> {
273    unix_connect(formatter, path).chain_err(|| ErrorKind::Initialization)
274}
275
276#[cfg(not(unix))]
277pub fn unix_custom<P: AsRef<Path>, F>(_formatter: F, _path: P) -> Result<Logger<LoggerBackend, F>> {
278    Err(ErrorKind::UnsupportedPlatform)?
279}
280
281#[cfg(unix)]
282fn unix_connect<P: AsRef<Path>, F>(formatter: F, path: P) -> Result<Logger<LoggerBackend, F>> {
283    let sock = UnixDatagram::unbound()?;
284    match sock.connect(&path) {
285        Ok(()) => Ok(Logger {
286            formatter,
287            backend: LoggerBackend::Unix(sock),
288        }),
289        Err(ref e) if e.raw_os_error() == Some(libc::EPROTOTYPE) => {
290            let sock = UnixStream::connect(path)?;
291            Ok(Logger {
292                formatter,
293                backend: LoggerBackend::UnixStream(BufWriter::new(sock)),
294            })
295        }
296        Err(e) => Err(e.into()),
297    }
298}
299
300/// returns a UDP logger connecting `local` and `server`
301pub fn udp<T: ToSocketAddrs, F>(
302    formatter: F,
303    local: T,
304    server: T,
305) -> Result<Logger<LoggerBackend, F>> {
306    server
307        .to_socket_addrs()
308        .chain_err(|| ErrorKind::Initialization)
309        .and_then(|mut server_addr_opt| {
310            server_addr_opt
311                .next()
312                .chain_err(|| ErrorKind::Initialization)
313        })
314        .and_then(|server_addr| {
315            UdpSocket::bind(local)
316                .chain_err(|| ErrorKind::Initialization)
317                .and_then(|socket| {
318                    Ok(Logger {
319                        formatter,
320                        backend: LoggerBackend::Udp(socket, server_addr),
321                    })
322                })
323        })
324}
325
326/// returns a TCP logger connecting to `server`
327pub fn tcp<T: ToSocketAddrs, F>(formatter: F, server: T) -> Result<Logger<LoggerBackend, F>> {
328    TcpStream::connect(server)
329        .chain_err(|| ErrorKind::Initialization)
330        .and_then(|socket| {
331            Ok(Logger {
332                formatter,
333                backend: LoggerBackend::Tcp(BufWriter::new(socket)),
334            })
335        })
336}
337/// returns a TLS logger connecting to `server`, using `cert` checked against `host_domain`.
338pub fn tls<F>(
339    formatter: F,
340    server: SocketAddr,
341    cert: Certificate,
342    host_domain: &str,
343) -> Result<Logger<LoggerBackend, F>> {
344    // Start connection
345    let connector = TlsConnector::builder()
346        .add_root_certificate(cert)
347        .build()
348        .chain_err(|| "Failed to build TLS connector.")?;
349    let stream = TcpStream::connect(server).chain_err(|| "Failed to initialize TCP socket.")?;
350    let stream = connector
351        .connect(host_domain, stream)
352        .chain_err(|| "Failed to initialize TLS over the TCP stream.")?;
353    Ok(Logger {
354        formatter,
355        backend: LoggerBackend::Tls(BufWriter::new(stream)),
356    })
357}
358
359pub struct BasicLogger {
360    logger: Arc<Mutex<Logger<LoggerBackend, Formatter3164>>>,
361}
362
363impl BasicLogger {
364    pub fn new(logger: Logger<LoggerBackend, Formatter3164>) -> BasicLogger {
365        BasicLogger {
366            logger: Arc::new(Mutex::new(logger)),
367        }
368    }
369}
370
371#[allow(unused_variables, unused_must_use)]
372impl Log for BasicLogger {
373    fn enabled(&self, metadata: &Metadata) -> bool {
374        metadata.level() <= log::max_level() && metadata.level() <= log::STATIC_MAX_LEVEL
375    }
376
377    fn log(&self, record: &Record) {
378        //FIXME: temporary patch to compile
379        let message = format!("{}", record.args());
380        let mut logger = self.logger.lock().unwrap();
381        match record.level() {
382            Level::Error => logger.err(&message),
383            Level::Warn => logger.warning(&message),
384            Level::Info => logger.info(&message),
385            Level::Debug => logger.debug(&message),
386            Level::Trace => logger.debug(&message),
387        };
388    }
389
390    fn flush(&self) {
391        let _ = self.logger.lock().unwrap().backend.flush();
392    }
393}
394
395/// Unix socket Logger init function compatible with log crate
396#[cfg(unix)]
397pub fn init_unix(facility: Facility, log_level: log::LevelFilter) -> Result<()> {
398    let (process, pid) = get_process_info()?;
399    let formatter = Formatter3164 {
400        facility,
401        hostname: None,
402        process,
403        pid,
404    };
405    unix(formatter).and_then(|logger| {
406        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
407            .chain_err(|| ErrorKind::Initialization)
408    })?;
409
410    log::set_max_level(log_level);
411    Ok(())
412}
413
414#[cfg(not(unix))]
415pub fn init_unix(_facility: Facility, _log_level: log::LevelFilter) -> Result<()> {
416    Err(ErrorKind::UnsupportedPlatform)?
417}
418
419/// Unix socket Logger init function compatible with log crate and user provided socket path
420#[cfg(unix)]
421pub fn init_unix_custom<P: AsRef<Path>>(
422    facility: Facility,
423    log_level: log::LevelFilter,
424    path: P,
425) -> Result<()> {
426    let (process, pid) = get_process_info()?;
427    let formatter = Formatter3164 {
428        facility,
429        hostname: None,
430        process,
431        pid,
432    };
433    unix_custom(formatter, path).and_then(|logger| {
434        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
435            .chain_err(|| ErrorKind::Initialization)
436    })?;
437
438    log::set_max_level(log_level);
439    Ok(())
440}
441
442#[cfg(not(unix))]
443pub fn init_unix_custom<P: AsRef<Path>>(
444    _facility: Facility,
445    _log_level: log::LevelFilter,
446    _path: P,
447) -> Result<()> {
448    Err(ErrorKind::UnsupportedPlatform)?
449}
450
451/// UDP Logger init function compatible with log crate
452pub fn init_udp<T: ToSocketAddrs>(
453    local: T,
454    server: T,
455    hostname: String,
456    facility: Facility,
457    log_level: log::LevelFilter,
458) -> Result<()> {
459    let (process, pid) = get_process_info()?;
460    let formatter = Formatter3164 {
461        facility,
462        hostname: Some(hostname),
463        process,
464        pid,
465    };
466    udp(formatter, local, server).and_then(|logger| {
467        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
468            .chain_err(|| ErrorKind::Initialization)
469    })?;
470
471    log::set_max_level(log_level);
472    Ok(())
473}
474
475/// TCP Logger init function compatible with log crate
476pub fn init_tcp<T: ToSocketAddrs>(
477    server: T,
478    hostname: String,
479    facility: Facility,
480    log_level: log::LevelFilter,
481) -> Result<()> {
482    let (process, pid) = get_process_info()?;
483    let formatter = Formatter3164 {
484        facility,
485        hostname: Some(hostname),
486        process,
487        pid,
488    };
489
490    tcp(formatter, server).and_then(|logger| {
491        log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
492            .chain_err(|| ErrorKind::Initialization)
493    })?;
494
495    log::set_max_level(log_level);
496    Ok(())
497}
498
499/// Initializes logging subsystem for log crate
500///
501/// This tries to connect to syslog by following ways:
502///
503/// 1. Unix sockets /dev/log and /var/run/syslog (in this order)
504/// 2. Tcp connection to 127.0.0.1:601
505/// 3. Udp connection to 127.0.0.1:514
506///
507/// Note the last option usually (almost) never fails in this method. So
508/// this method doesn't return error even if there is no syslog.
509///
510/// If `application_name` is `None` name is derived from executable name
511pub fn init(
512    facility: Facility,
513    log_level: log::LevelFilter,
514    application_name: Option<&str>,
515) -> Result<()> {
516    let (process_name, pid) = get_process_info()?;
517    let process = application_name.map(From::from).unwrap_or(process_name);
518    let mut formatter = Formatter3164 {
519        facility,
520        hostname: None,
521        process,
522        pid,
523    };
524
525    let backend = if let Ok(logger) = unix(formatter.clone()) {
526        logger.backend
527    } else {
528        formatter.hostname = get_hostname().ok();
529        if let Ok(tcp_stream) = TcpStream::connect(("127.0.0.1", 601)) {
530            LoggerBackend::Tcp(BufWriter::new(tcp_stream))
531        } else {
532            let udp_addr = "127.0.0.1:514".parse().unwrap();
533            let udp_stream = UdpSocket::bind(("127.0.0.1", 0))?;
534            LoggerBackend::Udp(udp_stream, udp_addr)
535        }
536    };
537    log::set_boxed_logger(Box::new(BasicLogger::new(Logger { formatter, backend })))
538        .chain_err(|| ErrorKind::Initialization)?;
539
540    log::set_max_level(log_level);
541    Ok(())
542}
543
544fn get_process_info() -> Result<(String, u32)> {
545    env::current_exe()
546        .chain_err(|| ErrorKind::Initialization)
547        .and_then(|path| {
548            path.file_name()
549                .and_then(|os_name| os_name.to_str())
550                .map(|name| name.to_string())
551                .chain_err(|| ErrorKind::Initialization)
552        })
553        .map(|name| (name, process::id()))
554}
555
556fn get_hostname() -> Result<String> {
557    Ok(hostname::get()?.to_string_lossy().to_string())
558}