use crate::{writers::log_writer::LogWriter, DeferredNow};
use std::io::{Error as IoError, ErrorKind, Result as IoResult, Write};
use std::net::{TcpStream, ToSocketAddrs, UdpSocket};
#[cfg(target_family = "unix")]
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: String,
process: String,
pid: u32,
facility: SyslogFacility,
message_id: String,
determine_severity: LevelToSyslogSeverity,
m_conn_buf: Mutex<ConnectorAndBuffer>,
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: Syslog,
) -> IoResult<Box<Self>> {
const UNKNOWN_HOSTNAME: &str = "<unknown_hostname>";
let hostname = hostname::get().map_or_else(
|_| Ok(UNKNOWN_HOSTNAME.to_owned()),
|s| {
s.into_string().map_err(|_| {
IoError::new(
ErrorKind::InvalidData,
"Hostname contains non-UTF8 characters".to_owned(),
)
})
},
)?;
let process = std::env::args().next().ok_or_else(|| {
IoError::new(
ErrorKind::Other,
"Can't infer app name as no env args are present".to_owned(),
)
})?;
Ok(Box::new(Self {
hostname,
process,
pid: std::process::id(),
facility,
max_log_level,
message_id,
determine_severity: match determine_severity {
Some(f) => f,
None => default_mapping,
},
m_conn_buf: Mutex::new(ConnectorAndBuffer {
conn: syslog.into_inner(),
buf: Vec::with_capacity(200),
}),
}))
}
}
impl LogWriter for SyslogWriter {
fn write(&self, now: &mut DeferredNow, record: &log::Record) -> IoResult<()> {
let mut conn_buf_guard = self
.m_conn_buf
.lock()
.map_err(|_| crate::util::io_err("SyslogWriter is poisoned"))?;
let cb = &mut *conn_buf_guard;
let severity = (self.determine_severity)(record.level());
cb.buf.clear();
#[allow(clippy::write_literal)]
write!(
cb.buf,
"<{pri}>{version} {timestamp} {hostname} {appname} {procid} {msgid} - {msg}",
pri = self.facility as u8 | severity as u8,
version = "1",
timestamp = now.format_rfc3339(),
hostname = self.hostname,
appname = self.process,
procid = self.pid,
msgid = self.message_id,
msg = &record.args()
)?;
cb.conn.write_all(&cb.buf)
}
fn flush(&self) -> IoResult<()> {
self.m_conn_buf
.lock()
.map_err(|_| crate::util::io_err("SyslogWriter is poisoned"))?
.conn
.flush()
}
fn max_log_level(&self) -> log::LevelFilter {
self.max_log_level
}
}
struct ConnectorAndBuffer {
conn: SyslogConnector,
buf: Vec<u8>,
}
pub struct Syslog(SyslogConnector);
impl Syslog {
#[cfg_attr(docsrs, doc(cfg(target_family = "unix")))]
#[cfg(target_family = "unix")]
pub fn try_datagram<P: AsRef<Path>>(path: P) -> IoResult<Self> {
let ud = std::os::unix::net::UnixDatagram::unbound()?;
ud.connect(&path)?;
Ok(Syslog(SyslogConnector::Datagram(ud)))
}
#[cfg_attr(docsrs, doc(cfg(target_family = "unix")))]
#[cfg(target_family = "unix")]
pub fn try_stream<P: AsRef<Path>>(path: P) -> IoResult<Self> {
Ok(Syslog(SyslogConnector::Stream(
std::os::unix::net::UnixStream::connect(path)?,
)))
}
pub fn try_tcp<T: ToSocketAddrs>(server: T) -> IoResult<Self> {
Ok(Syslog(SyslogConnector::Tcp(TcpStream::connect(server)?)))
}
pub fn try_udp<T: ToSocketAddrs>(local: T, server: T) -> IoResult<Self> {
let socket = UdpSocket::bind(local)?;
socket.connect(server)?;
Ok(Syslog(SyslogConnector::Udp(socket)))
}
fn into_inner(self) -> SyslogConnector {
self.0
}
}
#[derive(Debug)]
enum SyslogConnector {
#[cfg_attr(docsrs, doc(cfg(target_family = "unix")))]
#[cfg(target_family = "unix")]
Stream(std::os::unix::net::UnixStream),
#[cfg_attr(docsrs, doc(cfg(target_family = "unix")))]
#[cfg(target_family = "unix")]
Datagram(std::os::unix::net::UnixDatagram),
Udp(UdpSocket),
Tcp(TcpStream),
}
impl Write for SyslogConnector {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
#[allow(clippy::match_same_arms)]
match *self {
#[cfg(target_family = "unix")]
Self::Datagram(ref ud) => {
ud.send(buf)
}
#[cfg(target_family = "unix")]
Self::Stream(ref mut w) => {
w.write(buf).and_then(|sz| w.write_all(&[0; 1]).map(|_| sz))
}
Self::Tcp(ref mut w) => {
w.write(buf)
}
Self::Udp(ref socket) => {
socket.send(buf)
}
}
}
#[allow(clippy::match_same_arms)]
fn flush(&mut self) -> IoResult<()> {
match *self {
#[cfg(target_family = "unix")]
Self::Datagram(_) => Ok(()),
#[cfg(target_family = "unix")]
Self::Stream(ref mut w) => w.flush(),
Self::Udp(_) => Ok(()),
Self::Tcp(ref mut w) => w.flush(),
}
}
}