use super::format::MsgFormat;
use super::SyslogBuilder;
use libc::{c_char, c_int};
use once_cell::sync::Lazy;
use slog::{Drain, Level, OwnedKVList, Record};
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::ptr;
use std::result::Result as StdResult;
use std::sync::{Arc, Mutex, MutexGuard};
#[cfg(test)]
use super::mock::{self, closelog, openlog, syslog};
#[cfg(not(test))]
use libc::{closelog, openlog, syslog};
#[allow(clippy::mutex_atomic)]
static LAST_UNIQUE_IDENT: Lazy<Mutex<usize>> =
Lazy::new(|| Mutex::new(ptr::null::<c_char>() as usize));
pub(super) struct SyslogDrain {
unique_ident: Option<Box<CStr>>,
format: Arc<dyn MsgFormat>,
}
impl SyslogDrain {
pub fn new(builder: &SyslogBuilder) -> Self {
let (ident, unique_ident): (*const c_char, Option<Box<CStr>>) = match builder.ident.clone()
{
Some(Cow::Owned(ident_s)) => {
let unique_ident = ident_s.into_boxed_c_str();
(unique_ident.as_ptr(), Some(unique_ident))
}
Some(Cow::Borrowed(ident_s)) => (ident_s.as_ptr(), None),
None => (ptr::null(), None),
};
{
let mut last_unique_ident: MutexGuard<usize> = LAST_UNIQUE_IDENT.lock().unwrap();
unsafe {
openlog(ident, builder.option, builder.facility.into());
}
if !ident.is_null() {
*last_unique_ident = match &unique_ident {
Some(s) => s.as_ptr(),
None => ptr::null::<c_char>(),
} as usize;
}
}
SyslogDrain {
unique_ident,
format: builder.format.clone(),
}
}
}
impl Drop for SyslogDrain {
fn drop(&mut self) {
if let Some(my_ident) = self.unique_ident.take() {
let mut last_unique_ident: MutexGuard<usize> = match LAST_UNIQUE_IDENT.lock() {
Ok(locked) => locked,
Err(_) => {
Box::leak(my_ident);
return;
}
};
if my_ident.as_ptr() as usize == *last_unique_ident {
unsafe {
closelog();
}
*last_unique_ident = ptr::null::<c_char>() as usize;
}
#[cfg(test)]
mock::push_event(mock::Event::DropOwnedIdent(String::from(
my_ident.to_string_lossy(),
)));
}
}
}
impl Drain for SyslogDrain {
type Ok = ();
type Err = slog::Never;
fn log(&self, record: &Record, values: &OwnedKVList) -> StdResult<Self::Ok, Self::Err> {
let (msg, fmt_err) = match MsgFormat::to_string(self.format.as_ref(), record, values) {
Ok(msg) => (msg, None),
Err(fmt_err) => (record.msg().to_string(), Some(fmt_err.to_string())),
};
let msg = to_cstring_lossy(msg);
let fmt_err = fmt_err.map(to_cstring_lossy);
let priority: c_int = match record.level() {
Level::Critical => libc::LOG_CRIT,
Level::Error => libc::LOG_ERR,
Level::Warning => libc::LOG_WARNING,
Level::Debug | Level::Trace => libc::LOG_DEBUG,
_ => libc::LOG_INFO,
};
unsafe {
syslog(
priority,
CStr::from_bytes_with_nul_unchecked(b"%s\0").as_ptr(),
msg.as_ptr(),
);
}
if let Some(fmt_err) = fmt_err {
unsafe {
syslog(
libc::LOG_ERR,
CStr::from_bytes_with_nul_unchecked(
b"Error fully formatting the previous log message: %s\0",
)
.as_ptr(),
fmt_err.as_ptr(),
);
}
}
Ok(())
}
}
fn to_cstring_lossy(s: String) -> CString {
let mut s: Vec<u8> = s.into();
s.retain(|b| *b != 0);
unsafe { CString::from_vec_unchecked(s) }
}