use crate::{writers::log_writer::LogWriter, DeferredNow};
use std::cell::RefCell;
use std::ffi::OsString;
use std::io::Error as IoError;
use std::io::Result as IoResult;
use std::io::{BufWriter, ErrorKind, Write};
use std::net::{TcpStream, ToSocketAddrs, UdpSocket};
#[cfg(target_os = "linux")]
use std::path::Path;
use std::sync::Mutex;
#[derive(Copy, Clone, Debug)]
pub enum SyslogFacility {
Kernel = 0 << 3,
UserLevel = 1 << 3,
MailSystem = 2 << 3,
SystemDaemons = 3 << 3,
Authorization = 4 << 3,
SyslogD = 5 << 3,
LinePrinter = 6 << 3,
News = 7 << 3,
Uucp = 8 << 3,
Clock = 9 << 3,
Authorization2 = 10 << 3,
Ftp = 11 << 3,
Ntp = 12 << 3,
LogAudit = 13 << 3,
LogAlert = 14 << 3,
Clock2 = 15 << 3,
LocalUse0 = 16 << 3,
LocalUse1 = 17 << 3,
LocalUse2 = 18 << 3,
LocalUse3 = 19 << 3,
LocalUse4 = 20 << 3,
LocalUse5 = 21 << 3,
LocalUse6 = 22 << 3,
LocalUse7 = 23 << 3,
}
#[derive(Debug)]
pub enum SyslogSeverity {
Emergency = 0,
Alert = 1,
Critical = 2,
Error = 3,
Warning = 4,
Notice = 5,
Info = 6,
Debug = 7,
}
pub type LevelToSyslogSeverity = fn(level: log::Level) -> SyslogSeverity;
fn default_mapping(level: log::Level) -> SyslogSeverity {
match level {
log::Level::Error => SyslogSeverity::Error,
log::Level::Warn => SyslogSeverity::Warning,
log::Level::Info => SyslogSeverity::Info,
log::Level::Debug | log::Level::Trace => SyslogSeverity::Debug,
}
}
pub struct SyslogWriter {
hostname: OsString,
process: String,
pid: u32,
facility: SyslogFacility,
message_id: String,
determine_severity: LevelToSyslogSeverity,
syslog: Mutex<RefCell<SyslogConnector>>,
max_log_level: log::LevelFilter,
}
impl SyslogWriter {
pub fn try_new(
facility: SyslogFacility,
determine_severity: Option<LevelToSyslogSeverity>,
max_log_level: log::LevelFilter,
message_id: String,
syslog: SyslogConnector,
) -> IoResult<Box<Self>> {
Ok(Box::new(Self {
hostname: hostname::get().unwrap_or_else(|_| OsString::from("<unknown_hostname>")),
process: std::env::args()
.next()
.ok_or_else(|| IoError::new(ErrorKind::Other, "<no progname>".to_owned()))?,
pid: std::process::id(),
facility,
max_log_level,
message_id,
determine_severity: match determine_severity {
Some(f) => f,
None => default_mapping,
},
syslog: Mutex::new(RefCell::new(syslog)),
}))
}
}
impl LogWriter for SyslogWriter {
fn write(&self, now: &mut DeferredNow, record: &log::Record) -> IoResult<()> {
let mr_syslog = self.syslog.lock().unwrap();
let mut syslog = mr_syslog.borrow_mut();
let severity = (self.determine_severity)(record.level());
writeln!(
syslog,
"<{}>1 {} {:?} {} {} {} - {}",
self.facility as u8 | severity as u8,
now.format_rfc3339(),
self.hostname,
self.process,
self.pid,
self.message_id,
&record.args()
)
}
fn flush(&self) -> IoResult<()> {
let mr_syslog = self.syslog.lock().unwrap();
let mut syslog = mr_syslog.borrow_mut();
syslog.flush()?;
Ok(())
}
fn max_log_level(&self) -> log::LevelFilter {
self.max_log_level
}
}
#[derive(Debug)]
pub enum SyslogConnector {
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(target_os = "linux")]
Stream(BufWriter<std::os::unix::net::UnixStream>),
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(target_os = "linux")]
Datagram(std::os::unix::net::UnixDatagram),
Udp(UdpSocket),
Tcp(BufWriter<TcpStream>),
}
impl SyslogConnector {
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(target_os = "linux")]
pub fn try_datagram<P: AsRef<Path>>(path: P) -> IoResult<SyslogConnector> {
let ud = std::os::unix::net::UnixDatagram::unbound()?;
ud.connect(&path)?;
Ok(SyslogConnector::Datagram(ud))
}
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(target_os = "linux")]
pub fn try_stream<P: AsRef<Path>>(path: P) -> IoResult<SyslogConnector> {
Ok(SyslogConnector::Stream(BufWriter::new(
std::os::unix::net::UnixStream::connect(path)?,
)))
}
pub fn try_tcp<T: ToSocketAddrs>(server: T) -> IoResult<Self> {
Ok(Self::Tcp(BufWriter::new(TcpStream::connect(server)?)))
}
pub fn try_udp<T: ToSocketAddrs>(local: T, server: T) -> IoResult<Self> {
let socket = UdpSocket::bind(local)?;
socket.connect(server)?;
Ok(Self::Udp(socket))
}
}
impl Write for SyslogConnector {
fn write(&mut self, message: &[u8]) -> IoResult<usize> {
#[allow(clippy::match_same_arms)]
match *self {
#[cfg(target_os = "linux")]
Self::Datagram(ref ud) => {
ud.send(message)
}
#[cfg(target_os = "linux")]
Self::Stream(ref mut w) => {
w.write(message)
.and_then(|sz| w.write_all(&[0; 1]).map(|_| sz))
}
Self::Tcp(ref mut w) => {
w.write(message)
}
Self::Udp(ref socket) => {
socket.send(message)
}
}
}
#[allow(clippy::match_same_arms)]
fn flush(&mut self) -> IoResult<()> {
match *self {
#[cfg(target_os = "linux")]
Self::Datagram(_) => Ok(()),
#[cfg(target_os = "linux")]
Self::Stream(ref mut w) => w.flush(),
Self::Udp(_) => Ok(()),
Self::Tcp(ref mut w) => w.flush(),
}
}
}