1#[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
84pub 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 #[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 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 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#[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 } 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#[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
300pub 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
326pub 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}
337pub fn tls<F>(
339 formatter: F,
340 server: SocketAddr,
341 cert: Certificate,
342 host_domain: &str,
343) -> Result<Logger<LoggerBackend, F>> {
344 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 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#[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#[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
451pub 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
475pub 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
499pub 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}