Skip to main content

syslog_too/
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 [GitLab](https://gitlab.com/ngreese/syslog-too)
9//!
10//! # Example
11//!
12//! ```rust
13//! use syslog_too::{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_too::{Facility, Formatter3164, Logger3164};
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_too::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(Logger3164::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;
58#[cfg(not(unix))]
59use std::io;
60use std::io::{BufWriter, ErrorKind};
61use std::net::{TcpStream, ToSocketAddrs, UdpSocket};
62#[cfg(unix)]
63use std::os::unix::net::{UnixDatagram, UnixStream};
64use std::path::Path;
65use std::process;
66
67mod errors;
68mod facility;
69mod format;
70mod logger;
71#[cfg(test)]
72mod tests;
73
74pub use errors::*;
75pub use facility::Facility;
76pub use format::Severity;
77pub use format::{Formatter3164, Formatter5424, LogFormat};
78pub use logger::{Logger, Logger3164, Logger5424, LoggerBackend};
79
80pub type Priority = u8;
81
82#[cfg(unix)]
83const UNIX_SOCK_PATHS: [&str; 3] = ["/dev/log", "/var/run/syslog", "/var/run/log"];
84
85/// Returns a Logger using unix socket to target local syslog ( using /dev/log or /var/run/syslog)
86#[cfg(unix)]
87pub fn unix<F: Clone>(formatter: F) -> Result<Logger<LoggerBackend, F>> {
88    UNIX_SOCK_PATHS
89        .iter()
90        .find_map(|path| {
91            unix_connect(formatter.clone(), *path).map_or_else(
92                |e| {
93                    if let Error::Io(ref io_err) = e {
94                        if io_err.kind() == ErrorKind::NotFound {
95                            None // not considered an error, try the next path
96                        } else {
97                            Some(Err(e))
98                        }
99                    } else {
100                        Some(Err(e))
101                    }
102                },
103                |logger| Some(Ok(logger)),
104            )
105        })
106        .transpose()
107        .map_err(|e| Error::Initialization(Box::new(e)))?
108        .ok_or_else(|| Error::Initialization("unix socket paths not found".into()))
109}
110
111#[cfg(not(unix))]
112pub fn unix<F: Clone>(_formatter: F) -> Result<Logger<LoggerBackend, F>> {
113    Err(io::Error::from(ErrorKind::Unsupported))?
114}
115
116/// Returns a Logger using unix socket to target local syslog at user provided path
117#[cfg(unix)]
118pub fn unix_custom<P: AsRef<Path>, F>(formatter: F, path: P) -> Result<Logger<LoggerBackend, F>> {
119    unix_connect(formatter, path).map_err(|e| Error::Initialization(Box::new(e)))
120}
121
122#[cfg(not(unix))]
123pub fn unix_custom<P: AsRef<Path>, F>(_formatter: F, _path: P) -> Result<Logger<LoggerBackend, F>> {
124    Err(io::Error::from(ErrorKind::Unsupported))?
125}
126
127#[cfg(unix)]
128fn unix_connect<P: AsRef<Path>, F>(formatter: F, path: P) -> Result<Logger<LoggerBackend, F>> {
129    let sock = UnixDatagram::unbound()?;
130    match sock.connect(&path) {
131        Ok(()) => Ok(Logger {
132            formatter,
133            backend: LoggerBackend::Unix(sock),
134        }),
135        Err(ref e) if e.raw_os_error() == Some(libc::EPROTOTYPE) => {
136            let sock = UnixStream::connect(path)?;
137            Ok(Logger {
138                formatter,
139                backend: LoggerBackend::UnixStream(BufWriter::new(sock)),
140            })
141        }
142        Err(e) => Err(e.into()),
143    }
144}
145
146/// returns a UDP logger connecting `local` and `server`
147pub fn udp<T: ToSocketAddrs, U: ToSocketAddrs, F>(
148    formatter: F,
149    local: T,
150    server: U,
151) -> Result<Logger<LoggerBackend, F>> {
152    server
153        .to_socket_addrs()
154        .map_err(|e| Error::Initialization(Box::new(e)))
155        .and_then(|mut server_addr_opt| {
156            server_addr_opt
157                .next()
158                .ok_or_else(|| Error::Initialization("no server address".into()))
159        })
160        .and_then(|server_addr| {
161            UdpSocket::bind(local)
162                .map_err(|e| Error::Initialization(Box::new(e)))
163                .map(|socket| Logger {
164                    formatter,
165                    backend: LoggerBackend::Udp(socket, server_addr),
166                })
167        })
168}
169
170/// returns a TCP logger connecting `local` and `server`
171pub fn tcp<T: ToSocketAddrs, F>(formatter: F, server: T) -> Result<Logger<LoggerBackend, F>> {
172    TcpStream::connect(server)
173        .map_err(|e| Error::Initialization(e.into()))
174        .map(|socket| Logger {
175            formatter,
176            backend: LoggerBackend::Tcp(BufWriter::new(socket)),
177        })
178}
179
180/// Unix socket Logger init function compatible with log crate
181#[cfg(unix)]
182pub fn init_unix(facility: Facility, log_level: log::LevelFilter) -> Result<()> {
183    let (process, pid) = get_process_info()?;
184    let formatter = Formatter3164 {
185        facility,
186        hostname: None,
187        process,
188        pid,
189    };
190    unix(formatter).and_then(|logger| {
191        log::set_boxed_logger(Box::new(Logger3164::new(logger)))
192            .map_err(|e| Error::Initialization(Box::new(e)))
193    })?;
194
195    log::set_max_level(log_level);
196    Ok(())
197}
198
199#[cfg(not(unix))]
200pub fn init_unix(_facility: Facility, _log_level: log::LevelFilter) -> Result<()> {
201    Err(io::Error::from(ErrorKind::Unsupported))?
202}
203
204/// Unix socket Logger init function compatible with log crate and user provided socket path
205#[cfg(unix)]
206pub fn init_unix_custom<P: AsRef<Path>>(
207    facility: Facility,
208    log_level: log::LevelFilter,
209    path: P,
210) -> Result<()> {
211    let (process, pid) = get_process_info()?;
212    let formatter = Formatter3164 {
213        facility,
214        hostname: None,
215        process,
216        pid,
217    };
218    unix_custom(formatter, path).and_then(|logger| {
219        log::set_boxed_logger(Box::new(Logger3164::new(logger)))
220            .map_err(|e| Error::Initialization(Box::new(e)))
221    })?;
222
223    log::set_max_level(log_level);
224    Ok(())
225}
226
227#[cfg(not(unix))]
228pub fn init_unix_custom<P: AsRef<Path>>(
229    _facility: Facility,
230    _log_level: log::LevelFilter,
231    _path: P,
232) -> Result<()> {
233    Err(io::Error::from(ErrorKind::Unsupported))?
234}
235
236/// UDP Logger init function compatible with log crate
237pub fn init_udp<T: ToSocketAddrs>(
238    local: T,
239    server: T,
240    hostname: String,
241    facility: Facility,
242    log_level: log::LevelFilter,
243) -> Result<()> {
244    let (process, pid) = get_process_info()?;
245    let formatter = Formatter3164 {
246        facility,
247        hostname: Some(hostname),
248        process,
249        pid,
250    };
251    udp(formatter, local, server).and_then(|logger| {
252        log::set_boxed_logger(Box::new(Logger3164::new(logger)))
253            .map_err(|e| Error::Initialization(Box::new(e)))
254    })?;
255
256    log::set_max_level(log_level);
257    Ok(())
258}
259
260/// TCP Logger init function compatible with log crate
261pub fn init_tcp<T: ToSocketAddrs>(
262    server: T,
263    hostname: String,
264    facility: Facility,
265    log_level: log::LevelFilter,
266) -> Result<()> {
267    let (process, pid) = get_process_info()?;
268    let formatter = Formatter3164 {
269        facility,
270        hostname: Some(hostname),
271        process,
272        pid,
273    };
274
275    tcp(formatter, server).and_then(|logger| {
276        log::set_boxed_logger(Box::new(Logger3164::new(logger)))
277            .map_err(|e| Error::Initialization(Box::new(e)))
278    })?;
279
280    log::set_max_level(log_level);
281    Ok(())
282}
283
284/// Initializes logging subsystem for log crate
285///
286/// This tries to connect to syslog by following ways:
287///
288/// 1. Unix sockets /dev/log and /var/run/syslog (in this order)
289/// 2. Tcp connection to 127.0.0.1:601
290/// 3. Udp connection to 127.0.0.1:514
291///
292/// Note the last option usually (almost) never fails in this method. So
293/// this method doesn't return error even if there is no syslog.
294///
295/// If `application_name` is `None` name is derived from executable name
296pub fn init(
297    facility: Facility,
298    log_level: log::LevelFilter,
299    application_name: Option<&str>,
300) -> Result<()> {
301    let (process_name, pid) = get_process_info()?;
302    let process = application_name.map(From::from).unwrap_or(process_name);
303    let mut formatter = Formatter3164 {
304        facility,
305        hostname: None,
306        process,
307        pid,
308    };
309
310    let backend = if let Ok(logger) = unix(formatter.clone()) {
311        logger.backend
312    } else {
313        formatter.hostname = get_hostname().ok();
314        if let Ok(tcp_stream) = TcpStream::connect(("127.0.0.1", 601)) {
315            LoggerBackend::Tcp(BufWriter::new(tcp_stream))
316        } else {
317            let udp_addr = "127.0.0.1:514".parse().unwrap();
318            let udp_stream = UdpSocket::bind(("127.0.0.1", 0))?;
319            LoggerBackend::Udp(udp_stream, udp_addr)
320        }
321    };
322    log::set_boxed_logger(Box::new(Logger3164::new(Logger { formatter, backend })))
323        .map_err(|e| Error::Initialization(Box::new(e)))?;
324
325    log::set_max_level(log_level);
326    Ok(())
327}
328
329fn get_process_info() -> Result<(String, u32)> {
330    env::current_exe()
331        .map_err(|e| Error::Initialization(Box::new(e)))
332        .and_then(|path| {
333            path.file_name()
334                .and_then(|os_name| os_name.to_str())
335                .map(|name| name.to_string())
336                .ok_or_else(|| Error::Initialization("process name not found".into()))
337        })
338        .map(|name| (name, process::id()))
339}
340
341fn get_hostname() -> Result<String> {
342    Ok(hostname::get()?.to_string_lossy().to_string())
343}