use crate::{level::Level, message::Message, Result, ErrorKind::*};
use libc::{self, c_int};
use strum_macros::Display;
use std::{
ffi::CString,
fmt::Debug,
str::FromStr,
sync::{Arc, Mutex},
io::{BufWriter, Write},
fs::File as StdFile,
path::{Path, PathBuf},
};
#[doc(hidden)]
pub trait LogFacade: Debug + Send + Sync {
fn log(&self, level: Level, msg: Message) -> Result<()>;
fn is_syslog(&self) -> bool { false }
}
#[derive(Debug, Default, Display, Clone)]
pub enum FacadeVariant {
#[default]
None,
StdOut,
StdErr,
Syslog(String),
File(PathBuf, bool), }
impl FromStr for FacadeVariant {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"none" => Ok(Self::None),
"stdout" => Ok(Self::StdOut),
"stderr" => Ok(Self::StdErr),
"syslog" => Ok(Self::Syslog("user".to_string())),
"file" => Ok(Self::File("/tmp/hclog.log".into(), false)),
_ => Err(format!("Facade '{}' not exists or not implemented", s)),
}
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum FacadeScope {
None,
Global(Arc<(dyn 'static + LogFacade + Send + Sync)>),
Local(Arc<(dyn LogFacade + Send + Sync)>),
}
impl Default for FacadeScope {
fn default() -> Self {
Self::new(&FacadeVariant::None)
}
}
#[allow(dead_code)]
impl FacadeScope {
pub fn new(variant: &FacadeVariant) -> Self {
match variant {
FacadeVariant::None => Self::None,
FacadeVariant::StdOut => Self::Global(Arc::new(self::StdOut::init())),
FacadeVariant::StdErr => Self::Global(Arc::new(self::StdErr::init())),
FacadeVariant::Syslog(s) => Self::Global(Arc::new(self::Syslog::init(s))),
FacadeVariant::File(p, t) => Self::Global(Arc::new(self::File::init(p, *t))),
}
}
pub fn to_local(&self) -> Option<Self> {
match self {
Self::Global(ref a) => Some(Self::Local(Arc::clone(a))),
Self::None | Self::Local(_) => None,
}
}
pub fn inner(&self) -> Option<&Arc<dyn LogFacade + Send + Sync>> {
match *self {
Self::None => None,
Self::Global(ref f) | Self::Local(ref f) => Some(f),
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct StdOut { handle: std::io::Stdout }
impl StdOut {
fn init() -> Self { Self { handle: std::io::stdout() } }
}
impl LogFacade for StdOut {
#[cfg(not(test))]
fn log(&self, _lvl: Level, msg: Message) -> Result<()> {
let mut handle = self.handle.lock();
handle.write_all(msg.to_string().as_bytes())?;
handle.write_all(b"\n")?;
Ok(())
}
#[cfg(test)]
fn log(&self, _lvl: Level, msg: Message) -> Result<()> {
println!("{}", msg);
Ok(())
}
}
#[derive(Debug)]
pub struct StdErr { handle: std::io::Stderr }
impl StdErr {
fn init() -> Self { Self { handle: std::io::stderr() }}
}
impl LogFacade for StdErr {
fn log(&self, _lvl: Level, msg: Message) -> Result<()> {
let mut handle = self.handle.lock();
handle.write_all(msg.to_string().as_bytes())?;
handle.write_all(b"\n")?;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct Syslog {
facility: c_int,
}
impl Syslog {
fn init(opt: &str) -> Self {
let facility = match opt {
"kern" => libc::LOG_KERN,
"user" => libc::LOG_USER,
"mail" => libc::LOG_MAIL,
"daemon" => libc::LOG_DAEMON,
"auth" => libc::LOG_AUTH,
"syslog" => libc::LOG_SYSLOG,
"lpr" => libc::LOG_LPR,
"news" => libc::LOG_NEWS,
"uucp" => libc::LOG_UUCP,
"local0" => libc::LOG_LOCAL0,
"local1" => libc::LOG_LOCAL1,
"local2" => libc::LOG_LOCAL2,
"local3" => libc::LOG_LOCAL3,
"local4" => libc::LOG_LOCAL4,
"local5" => libc::LOG_LOCAL5,
"local6" => libc::LOG_LOCAL6,
"local7" => libc::LOG_LOCAL7,
_ => panic!("unknown syslog facility '{}'", opt),
};
Self { facility }
}
}
impl LogFacade for Syslog {
fn is_syslog(&self) -> bool {
true
}
fn log(&self, level: Level, msg: Message) -> Result<()> {
let lvl = match level {
Level::Off => return Ok(()),
Level::Emerg => libc::LOG_EMERG,
Level::Alert => libc::LOG_ALERT,
Level::Crit => libc::LOG_CRIT,
Level::Error => libc::LOG_ERR,
Level::Warn => libc::LOG_WARNING,
Level::Notice => libc::LOG_NOTICE,
Level::Info => libc::LOG_INFO,
d if d >= Level::Debug1 && d <= Level::Debug10 => libc::LOG_DEBUG,
_ => return Err(UnknownLogLevel),
};
let msg_raw = CString::new(msg.to_string())?;
let fmt = CString::new("%s".to_owned())?;
unsafe {
libc::syslog(
lvl | self.facility,
fmt.as_ptr(),
msg_raw.as_ptr(),
)
}
Ok(())
}
}
#[derive(Debug)]
pub struct File {
handle: Arc<Mutex<BufWriter<StdFile>>>,
}
impl File {
fn init<P: AsRef<Path>>(path: P, truncate: bool) -> Self {
let handle = std::fs::OpenOptions::new()
.create(true)
.append(!truncate)
.truncate(truncate)
.write(true)
.open(path.as_ref())
.expect("failed to open log file");
Self { handle: Arc::new(Mutex::new(BufWriter::new(handle))) }
}
}
impl LogFacade for File {
fn log(&self, _: Level, msg: Message) -> Result<()> {
let handle = Arc::clone(&self.handle);
{
let mut writer = handle.lock().unwrap();
writer.write_all(msg.to_string().as_bytes())?;
writer.write_all(b"\n")?;
writer.flush()?;
}
Ok(())
}
}