slog_syslog/
lib.rs

1//! Syslog drain for slog-rs
2//!
3//! ```
4//! extern crate slog;
5//! extern crate slog_syslog;
6//!
7//! use slog::*;
8//! use slog_syslog::Facility;
9//!
10//! fn main() {
11//!     let o = o!("build-id" => "8dfljdf");
12//!
13//!     // log to a local unix sock `/var/run/syslog`
14//!     match slog_syslog::SyslogBuilder::new()
15//!         .facility(Facility::LOG_USER)
16//!         .level(slog::Level::Debug)
17//!         .unix("/var/run/syslog")
18//!         .start() {
19//!         Ok(x) => {
20//!             let root = Logger::root(x.fuse(), o);
21//!         },
22//!         Err(e) => println!("Failed to start syslog on `var/run/syslog`. Error {:?}", e)
23//!     };
24//! }
25//! ```
26#![warn(missing_docs)]
27
28use slog::{Drain, Level, OwnedKVList, Record};
29use std::{fmt, io};
30use std::sync::Mutex;
31use std::cell::RefCell;
32use std::path::{Path, PathBuf};
33use std::net::SocketAddr;
34use std::io::{Error, ErrorKind};
35
36use slog::KV;
37
38pub use syslog::Facility;
39
40thread_local! {
41    static TL_BUF: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(128))
42}
43
44type SysLogger = syslog::Logger<syslog::LoggerBackend, syslog::Formatter3164>;
45
46#[inline]
47fn handle_syslog_error(e: syslog::Error) -> io::Error
48{
49    Error::new(ErrorKind::Other, e.to_string())
50}
51
52fn log_with_level(level: slog::Level, mut io: std::sync::MutexGuard<Box<SysLogger>>, buf: &str) -> io::Result<()> {
53    let err = match level {
54        Level::Critical => io.crit(&buf),
55        Level::Error => io.err(&buf),
56        Level::Warning => io.warning(&buf),
57        Level::Info => io.notice(&buf),
58        Level::Debug => io.info(&buf),
59        Level::Trace => io.debug(&buf),
60    };
61    err.map_err(handle_syslog_error)
62}
63
64/// Create a formatter with runtime metadata filled in. 
65///
66/// This follows ``get_process_info()`` in the syslog crate to some extent
67/// which is private.
68fn syslog_format3164(facility: syslog::Facility, hostname: Option<String>) -> syslog::Formatter3164 {
69    let path = std::env::current_exe()
70        .unwrap_or_else(|_| PathBuf::new());
71    let process = path.file_name()
72        .map(|file| file.to_string_lossy().into_owned())
73        .unwrap_or_else(|| String::new());
74
75    syslog::Formatter3164 {
76        facility,
77        hostname,
78        process,
79        pid: std::process::id() as i32,
80    }
81}
82
83/// Drain formatting records and writing them to a syslog ``Logger`
84///
85/// Uses mutex to serialize writes.
86/// TODO: Add one that does not serialize?
87pub struct Streamer3164 {
88    io: Mutex<Box<SysLogger>>,
89    format: Format3164,
90    level: Level,
91}
92
93#[cfg(debug_assertions)]
94fn get_default_level() -> Level {
95    if cfg!(feature = "max_level_trace") {
96        Level::Trace
97    } else if cfg!(feature = "max_level_debug") {
98        Level::Debug
99    } else if cfg!(feature = "max_level_info") {
100        Level::Info
101    } else if cfg!(feature = "max_level_warn") {
102        Level::Warning
103    } else if cfg!(feature = "max_level_error") {
104        Level::Error
105    } else { // max_level_off
106        Level::Critical
107    }
108}
109
110#[cfg(not(debug_assertions))]
111fn get_default_level() -> Level {
112    if cfg!(feature = "release_max_level_trace") {
113        Level::Trace
114    } else if cfg!(feature = "release_max_level_debug") {
115        Level::Debug
116    } else if cfg!(feature = "release_max_level_info") {
117        Level::Info
118    } else if cfg!(feature = "release_max_level_warn") {
119        Level::Warning
120    } else if cfg!(feature = "release_max_level_error") {
121        Level::Error
122    } else { // release_max_level_off
123        Level::Critical
124    }
125}
126
127impl Streamer3164 {
128    /// Create new syslog ``Streamer` using given `format` and logging level.
129    pub fn new_with_level(logger: Box<SysLogger>, level: Level) -> Self {
130        Streamer3164 {
131            io: Mutex::new(logger),
132            format: Format3164::new(),
133            level,
134        }
135    }
136
137    /// Create new syslog ``Streamer` using given `format` and the default logging level.
138    pub fn new(logger: Box<SysLogger>) -> Self {
139        let level = get_default_level();
140        Self::new_with_level(logger, level)
141    }
142}
143
144impl Drain for Streamer3164 {
145    type Err = io::Error;
146    type Ok = ();
147
148    fn log(&self, info: &Record, logger_values: &OwnedKVList) -> io::Result<()> {
149        if self.level > info.level() {
150            return Ok(())
151        }
152        TL_BUF.with(|buf| {
153            let mut buf = buf.borrow_mut();
154            let res = {
155                || {
156                    self.format.format(&mut *buf, info, logger_values)?;
157                    let io = 
158                        self.io
159                        .lock()
160                        .map_err(|_| Error::new(ErrorKind::Other, "locking error"))?;
161
162                    let buf = String::from_utf8_lossy(&buf);
163
164                    log_with_level(info.level(), io, &buf)
165                }
166            }();
167            buf.clear();
168            res
169        })
170    }
171}
172
173/// Formatter to format defined in RFC 3164
174pub struct Format3164;
175
176impl Format3164 {
177    /// Create new `Format3164`
178    pub fn new() -> Self {
179        Format3164
180    }
181
182    fn format(
183        &self,
184        io: &mut dyn io::Write,
185        record: &Record,
186        logger_kv: &OwnedKVList,
187    ) -> io::Result<()> {
188        write!(io, "{}", record.msg())?;
189
190        let mut ser = KSV::new(io);
191        {
192            logger_kv.serialize(record, &mut ser)?;
193            record.kv().serialize(record, &mut ser)?;
194        }
195        Ok(())
196    }
197}
198
199/// Key-Separator-Value serializer
200struct KSV<W: io::Write> {
201    io: W,
202}
203
204impl<W: io::Write> KSV<W> {
205    fn new(io: W) -> Self {
206        KSV { io: io }
207    }
208}
209
210impl<W: io::Write> slog::Serializer for KSV<W> {
211    fn emit_arguments(&mut self, key: &str, val: &fmt::Arguments) -> slog::Result {
212        write!(self.io, ", {}: {}", key, val)?;
213        Ok(())
214    }
215}
216
217enum SyslogKind {
218    Unix {
219        path: PathBuf,
220    },
221    Tcp {
222        server: SocketAddr,
223        hostname: String,
224    },
225    Udp {
226        local: SocketAddr,
227        host: SocketAddr,
228        hostname: String,
229    },
230}
231
232/// Builder pattern for constructing a syslog
233pub struct SyslogBuilder {
234    facility: Option<syslog::Facility>,
235    level: Level,
236    logkind: Option<SyslogKind>,
237}
238impl Default for SyslogBuilder {
239    fn default() -> Self {
240        Self {
241            facility: None,
242            level: Level::Trace,
243            logkind: None,
244        }
245    }
246}
247impl SyslogBuilder {
248    /// Build a default logger
249    ///
250    /// By default this will attempt to connect to (in order)
251    pub fn new() -> SyslogBuilder {
252        Self::default()
253    }
254
255    /// Set syslog Facility
256    pub fn facility(self, facility: syslog::Facility) -> Self {
257        let mut s = self;
258        s.facility = Some(facility);
259        s
260    }
261
262    /// Filter Syslog by level
263    pub fn level(self, lvl: slog::Level) -> Self {
264        let mut s = self;
265        s.level = lvl;
266        s
267    }
268
269    /// Remote UDP syslogging
270    pub fn udp<S: AsRef<str>>(self, local: SocketAddr, host: SocketAddr, hostname: S) -> Self {
271        let mut s = self;
272        let hostname = hostname.as_ref().to_string();
273        s.logkind = Some(SyslogKind::Udp {
274            local,
275            host,
276            hostname,
277        });
278        s
279    }
280
281    /// Remote TCP syslogging
282    pub fn tcp<S: AsRef<str>>(self, server: SocketAddr, hostname: S) -> Self {
283        let mut s = self;
284        let hostname = hostname.as_ref().to_string();
285        s.logkind = Some(SyslogKind::Tcp { server, hostname });
286        s
287    }
288
289    /// Local syslogging over a unix socket
290    pub fn unix<P: AsRef<Path>>(self, path: P) -> Self {
291        let mut s = self;
292        let path = path.as_ref().to_path_buf();
293        s.logkind = Some(SyslogKind::Unix { path });
294        s
295    }
296
297    /// Start running
298    pub fn start(self) -> io::Result<Streamer3164> {
299        let facility = match self.facility {
300            Option::Some(x) => x,
301            Option::None => {
302                return Err(Error::new(
303                    ErrorKind::Other,
304                    "facility must be provided to the builder",
305                ));
306            }
307        };
308        let logkind = match self.logkind {
309            Option::Some(l) => l,
310            Option::None => {
311                return Err(Error::new(
312                    ErrorKind::Other,
313                    "no logger kind provided, library does not know what do initialize",
314                ));
315            }
316        };
317        let log = match logkind {
318            SyslogKind::Unix { path } => {
319                let format = syslog_format3164(facility, None);
320                syslog::unix_custom(format, path).map_err(handle_syslog_error)?
321            }
322            SyslogKind::Udp {
323                local,
324                host,
325                hostname,
326            } => {
327                let format = syslog_format3164(facility, Some(hostname));
328                syslog::udp(format, local, host).map_err(handle_syslog_error)?
329            },
330            SyslogKind::Tcp { server, hostname } => {
331                let format = syslog_format3164(facility, Some(hostname));
332                syslog::tcp(format, server).map_err(handle_syslog_error)?
333            },
334        };
335        Ok(Streamer3164::new_with_level(Box::new(log), self.level))
336    }
337}
338
339/// `Streamer` to Unix syslog using RFC 3164 format
340pub fn unix_3164_with_level(facility: syslog::Facility, level: Level) -> io::Result<Streamer3164> {
341    let format = syslog_format3164(facility, None);
342    syslog::unix(format)
343        .map(Box::new)
344        .map(|logger| Streamer3164::new_with_level(logger, level))
345        .map_err(handle_syslog_error)
346}
347
348/// `Streamer` to Unix syslog using RFC 3164 format
349pub fn unix_3164(facility: syslog::Facility) -> io::Result<Streamer3164> {
350    let format = syslog_format3164(facility, None);
351    syslog::unix(format)
352        .map(Box::new)
353        .map(Streamer3164::new)
354        .map_err(handle_syslog_error)
355}