slog_syslog_jl/
lib.rs

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