log4rs_syslog/
syslog.rs

1use std;
2
3use libc;
4use log;
5use log4rs;
6#[cfg(feature = "file")]
7use serde;
8
9const DEFAULT_BUF_SIZE: usize = 4096;
10
11type PersistentBuf = std::io::Cursor<Vec<u8>>;
12
13thread_local! {
14    static PERSISTENT_BUF: std::cell::RefCell<PersistentBuf> =
15        std::cell::RefCell::new(PersistentBuf::new(Vec::with_capacity(DEFAULT_BUF_SIZE)));
16}
17
18struct BufWriter {}
19
20impl BufWriter {
21    fn new() -> Self {
22        PERSISTENT_BUF.with(|pers_buf| pers_buf.borrow_mut().set_position(0));
23        Self {}
24    }
25
26    fn as_c_str(&mut self) -> *const libc::c_char {
27        use std::io::Write;
28
29        PERSISTENT_BUF.with(|pers_buf| {
30            let mut pers_buf = pers_buf.borrow_mut();
31            pers_buf.write_all(&[0; 1]).unwrap();
32            pers_buf.get_ref().as_ptr() as *const libc::c_char
33        })
34    }
35}
36
37impl std::io::Write for BufWriter {
38    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
39        PERSISTENT_BUF.with(|pers_buf| pers_buf.borrow_mut().write(buf))
40    }
41
42    fn flush(&mut self) -> std::io::Result<()> {
43        PERSISTENT_BUF.with(|pers_buf| pers_buf.borrow_mut().flush())
44    }
45}
46
47impl log4rs::encode::Write for BufWriter {}
48
49/// Function for mapping rust's `log` levels to `libc`'s log levels.
50pub type LevelMap = Fn(log::Level) -> libc::c_int + Send + Sync;
51
52/// An appender which writes log invents into syslog using `libc`'s syslog() function.
53pub struct SyslogAppender {
54    encoder: Box<log4rs::encode::Encode>,
55    level_map: Option<Box<LevelMap>>,
56}
57
58impl std::fmt::Debug for SyslogAppender {
59    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
60        write!(
61            formatter,
62            "SyslogAppender {{encoder: {:?}, level_map: {}}}",
63            self.encoder,
64            match self.level_map {
65                Some(_) => "Some(_)",
66                None => "None",
67            }
68        )
69    }
70}
71
72impl SyslogAppender {
73    /// Create new builder for `SyslogAppender`.
74    pub fn builder() -> SyslogAppenderBuilder {
75        SyslogAppenderBuilder {
76            encoder: None,
77            openlog_args: None,
78            level_map: None,
79        }
80    }
81}
82
83impl log4rs::append::Append for SyslogAppender {
84    fn append(&self, record: &log::Record) -> std::result::Result<(), Box<std::error::Error + Sync + Send>> {
85        let mut buf = BufWriter::new();
86
87        self.encoder.encode(&mut buf, record)?;
88
89        let level = match self.level_map {
90            Some(ref level_map) => level_map(record.level()),
91
92            None => match record.level() {
93                log::Level::Error => libc::LOG_ERR,
94                log::Level::Warn => libc::LOG_WARNING,
95                log::Level::Info => libc::LOG_INFO,
96                log::Level::Debug | log::Level::Trace => libc::LOG_DEBUG,
97            },
98        };
99
100        unsafe {
101            libc::syslog(
102                level,
103                b"%s\0".as_ptr() as *const libc::c_char,
104                buf.as_c_str(),
105            );
106        }
107
108        Ok(())
109    }
110
111    fn flush(&self) {}
112}
113
114bitflags! {
115    /// Syslog option flags.
116    pub struct LogOption: libc::c_int {
117        /// Write directly to system console if there is an error while sending to system logger.
118        const LOG_CONS   = libc::LOG_CONS;
119        /// Open the connection immediately (normally, the connection is opened when the first message is logged).
120        const LOG_NDELAY = libc::LOG_NDELAY;
121        /// Don't wait for child processes that may have been created while logging the message.
122        /// The GNU C library does not create a child process, so this option has no effect on Linux.
123        const LOG_NOWAIT = libc::LOG_NOWAIT;
124        /// The converse of LOG_NDELAY; opening of the connection is delayed until syslog() is called.
125        /// This is the default, and need not be specified.
126        const LOG_ODELAY = libc::LOG_ODELAY;
127        /// Print to stderr as well. (Not in POSIX.1-2001 or POSIX.1-2008.)
128        const LOG_PERROR = libc::LOG_PERROR;
129        /// Include PID with each message.
130        const LOG_PID    = libc::LOG_PID;
131    }
132}
133
134#[cfg(feature = "file")]
135struct LogOptionVisitor;
136
137#[cfg(feature = "file")]
138impl<'de> serde::de::Visitor<'de> for LogOptionVisitor {
139    type Value = LogOption;
140
141    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
142        formatter.write_str("list of flags separated by \"|\"")
143    }
144
145    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
146    where
147        E: serde::de::Error,
148    {
149        let mut flags = LogOption::empty();
150
151        let value = value.trim();
152        if !value.is_empty() {
153            for str_flag in value.split('|') {
154                let str_flag = str_flag.trim();
155                match str_flag {
156                    "LOG_CONS" => flags |= LogOption::LOG_CONS,
157                    "LOG_NDELAY" => flags |= LogOption::LOG_NDELAY,
158                    "LOG_NOWAIT" => flags |= LogOption::LOG_NOWAIT,
159                    "LOG_ODELAY" => flags |= LogOption::LOG_ODELAY,
160                    "LOG_PERROR" => flags |= LogOption::LOG_PERROR,
161                    "LOG_PID" => flags |= LogOption::LOG_PID,
162                    unknown => return Err(E::custom(format!("Unknown syslog flag: \"{}\"", unknown))),
163                }
164            }
165        }
166
167        Ok(flags)
168    }
169}
170
171#[cfg(feature = "file")]
172impl<'de> serde::de::Deserialize<'de> for LogOption {
173    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174    where
175        D: serde::de::Deserializer<'de>,
176    {
177        deserializer.deserialize_str(LogOptionVisitor)
178    }
179}
180
181#[derive(Debug)]
182#[cfg_attr(feature = "file", derive(Deserialize))]
183/// The type of program.
184pub enum Facility {
185    /// Security/authorization.
186    Auth,
187    /// Security/authorization (private).
188    AuthPriv,
189    /// Clock daemon (cron and at).
190    Cron,
191    /// System daemons without separate facility value.
192    Daemon,
193    /// FTP daemon.
194    Ftp,
195    /// Kernel messages (these can't be generated from user processes).
196    Kern,
197    /// Reserved for local use.
198    Local0,
199    /// Reserved for local use.
200    Local1,
201    /// Reserved for local use.
202    Local2,
203    /// Reserved for local use.
204    Local3,
205    /// Reserved for local use.
206    Local4,
207    /// Reserved for local use.
208    Local5,
209    /// Reserved for local use.
210    Local6,
211    /// Reserved for local use.
212    Local7,
213    /// Line printer subsystem.
214    Lpr,
215    /// Mail subsystem.
216    Mail,
217    /// USENET news subsystem.
218    News,
219    /// Messages generated internally by syslogd.
220    Syslog,
221    /// Generic user-level messages. This is the default when not calling openlog().
222    User,
223    /// UUCP subsystem.
224    Uucp,
225}
226
227impl Into<libc::c_int> for Facility {
228    fn into(self) -> libc::c_int {
229        match self {
230            Facility::Auth => libc::LOG_AUTH,
231            Facility::AuthPriv => libc::LOG_AUTHPRIV,
232            Facility::Cron => libc::LOG_CRON,
233            Facility::Daemon => libc::LOG_DAEMON,
234            Facility::Ftp => libc::LOG_FTP,
235            Facility::Kern => libc::LOG_KERN,
236            Facility::Local0 => libc::LOG_LOCAL0,
237            Facility::Local1 => libc::LOG_LOCAL1,
238            Facility::Local2 => libc::LOG_LOCAL2,
239            Facility::Local3 => libc::LOG_LOCAL3,
240            Facility::Local4 => libc::LOG_LOCAL4,
241            Facility::Local5 => libc::LOG_LOCAL5,
242            Facility::Local6 => libc::LOG_LOCAL6,
243            Facility::Local7 => libc::LOG_LOCAL7,
244            Facility::Lpr => libc::LOG_LPR,
245            Facility::Mail => libc::LOG_MAIL,
246            Facility::News => libc::LOG_NEWS,
247            Facility::Syslog => libc::LOG_SYSLOG,
248            Facility::User => libc::LOG_USER,
249            Facility::Uucp => libc::LOG_UUCP,
250        }
251    }
252}
253
254struct OpenLogArgs {
255    ident: String,
256    log_option: LogOption,
257    facility: Facility,
258}
259
260struct IdentHolder {
261    ident: Option<String>,
262}
263
264impl IdentHolder {
265    fn new() -> Self {
266        Self { ident: None }
267    }
268
269    fn openlog(&mut self, mut args: OpenLogArgs) {
270        args.ident.push('\0');
271
272        unsafe {
273            libc::openlog(
274                args.ident.as_ptr() as *const libc::c_char,
275                args.log_option.bits(),
276                args.facility.into(),
277            );
278        }
279
280        // At least on Linux openlog() does not copy this string, so we should keep it available.
281        self.ident = Some(args.ident);
282    }
283
284    fn closelog(&mut self) {
285        if self.ident.is_some() {
286            unsafe {
287                libc::closelog();
288            }
289        }
290    }
291
292    fn no_openlog(&mut self) {
293        self.closelog();
294        self.ident = None;
295    }
296}
297
298impl Drop for IdentHolder {
299    fn drop(&mut self) {
300        // Currently this function is never used automatically because IdentHolder is created only by lazy_static.
301        self.closelog();
302    }
303}
304
305lazy_static! {
306    static ref IDENT_HOLDER: std::sync::Mutex<IdentHolder> = std::sync::Mutex::new(IdentHolder::new());
307}
308
309/// Builder for `SyslogAppender`.
310pub struct SyslogAppenderBuilder {
311    encoder: Option<Box<log4rs::encode::Encode>>,
312    openlog_args: Option<OpenLogArgs>,
313    level_map: Option<Box<LevelMap>>,
314}
315
316impl SyslogAppenderBuilder {
317    /// Set custom encoder.
318    pub fn encoder(mut self, encoder: Box<log4rs::encode::Encode>) -> Self {
319        self.encoder = Some(encoder);
320        self
321    }
322
323    /// Call openlog().
324    pub fn openlog(mut self, ident: &str, option: LogOption, facility: Facility) -> Self {
325        self.openlog_args = Some(OpenLogArgs {
326            ident: String::from(ident),
327            log_option: option,
328            facility,
329        });
330        self
331    }
332
333    /// Set custom log level mapping.
334    pub fn level_map(mut self, level_map: Box<LevelMap>) -> Self {
335        self.level_map = Some(level_map);
336        self
337    }
338
339    /// Consume builder and produce `SyslogAppender`.
340    pub fn build(self) -> SyslogAppender {
341        self.openlog_args.map_or_else(
342            || IDENT_HOLDER.lock().unwrap().no_openlog(),
343            |openlog_args| IDENT_HOLDER.lock().unwrap().openlog(openlog_args),
344        );
345
346        SyslogAppender {
347            encoder: self.encoder
348                .unwrap_or_else(|| Box::new(log4rs::encode::pattern::PatternEncoder::default())),
349            level_map: self.level_map,
350        }
351    }
352}
353
354#[cfg(all(feature = "unstable", test))]
355mod bench {
356    use test;
357
358    fn bench(bencher: &mut test::Bencher, data: &[u8]) {
359        use std::io::Write;
360
361        bencher.iter(|| {
362            let mut buf = super::BufWriter::new();
363            buf.write_all(data).unwrap();
364            buf.as_c_str()
365        })
366    }
367
368    #[bench]
369    fn buf_writer_no_realloc(bencher: &mut test::Bencher) {
370        bench(bencher, &[b'x'; super::DEFAULT_BUF_SIZE - 1])
371    }
372
373    #[bench]
374    fn buf_writer_realloc(bencher: &mut test::Bencher) {
375        bench(bencher, &[b'x'; super::DEFAULT_BUF_SIZE])
376    }
377}