use std::{borrow::Cow, env::{self, VarError}, str::FromStr, sync::OnceLock};
#[cfg(feature = "build_sync")]
use crate::{LogFacility, LogStat, Priority, };
#[cfg(feature = "build_ext_file")]
use crate::SyslogFile;
#[cfg(feature = "build_ext_tls")]
use crate::SyslogTls;
#[cfg(feature = "build_ext_net")]
use crate::{SyslogNetTcp, SyslogNetUdp};
#[cfg(feature = "build_with_queue")]
use crate::{SyQueued, sy_sync_queue::SyslogQueueChannel};
use crate::{
DynSyslog, SyShared, Syslog, SyslogDestination, SyslogLocal, error::{SyRes},
formatters::
{
DEFAULT_SYSLOG_FORMATTER_TYPE, FormatFile, FormatRfc3146, FormatRfc5424, SyslogFormatter,
SyslogFormatters
},
map_error, throw_error
};
pub const LOGGER_TYPE_LOCAL: &'static str = "local";
pub const LOGGER_TYPE_UDP: &'static str = "udp";
pub const LOGGER_TYPE_TCP: &'static str = "tcp";
pub const LOGGER_TYPE_TLS: &'static str = "tls";
pub const LOGGER_TYPE_FILE: &'static str = "file";
pub const SYSLOG_LOGGER_DST_URI: &'static str = "SYSLOG_LOGGER_DST_URI";
pub const SYSLOG_LOGGER_LOG_FACILITY: &'static str = "SYSLOG_LOGGER_LOG_FACILITY";
pub const SYSLOG_LOGGER_LOG_FORMATTER: &'static str = "SYSLOG_LOGGER_LOG_FORMATTER";
pub const SYSLOG_LOGGER_LOG_IDENTITY: &'static str = "SYSLOG_LOGGER_LOG_IDENTITY";
pub(crate) static LOGGER: OnceLock<Box<dyn DynSyslog>> = OnceLock::new();
fn derive_suffix(suffix: Option<&str>, env_name: &'static str) -> Cow<'static, str>
{
if let Some(su) = suffix
{
Cow::Owned([env_name, "_", su].concat())
}
else
{
Cow::Borrowed(env_name)
}
}
pub
fn attach_event_log_shared<F, D>(sy: &Syslog<F, D, SyShared>) -> SyRes<()>
where
F: SyslogFormatter,
D: SyslogDestination,
{
let clonned_sy = sy.clone().make_dyn();
LOGGER
.set(clonned_sy)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)
}
#[cfg(feature = "build_with_queue")]
pub
fn attach_event_log_queued<F, D, S>(sy: &Syslog<F, D, SyQueued<S, F, D>>) -> SyRes<()>
where
F: SyslogFormatter,
D: SyslogDestination,
S: SyslogQueueChannel<F, D>
{
let clonned_sy = sy.clone().make_dyn();
LOGGER
.set(clonned_sy)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)
}
fn set_logger(syslog_inst: Box<dyn DynSyslog>) -> SyRes<()>
{
LOGGER
.set(syslog_inst)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)
}
fn openlog_with<F, D>(ident: Option<String>, logstat: LogStat, facility: LogFacility, net_tap_prov: D) -> SyRes<Box<dyn DynSyslog>>
where
F: SyslogFormatter,
D: SyslogDestination,
{
Syslog
::<F, D, SyShared>
::openlog_with(ident.as_ref().map(|d| d.as_str()),
logstat, facility, net_tap_prov)
.map(|sy| sy.make_dyn())
}
#[derive(Debug)]
struct EnvVars
{
logger_type_res: String,
logger_facility: LogFacility,
logger_formatter: SyslogFormatters,
logger_identity: Option<String>,
}
impl EnvVars
{
fn read_env(env_sufix: Option<&str>) -> SyRes<Option<Self>>
{
let logger_type_res =
match env::var(derive_suffix(env_sufix, SYSLOG_LOGGER_DST_URI).as_ref())
{
Ok(val) =>
val,
Err(VarError::NotPresent) =>
return Ok(None),
Err(VarError::NotUnicode(_e)) =>
throw_error!("{} is not unicode", SYSLOG_LOGGER_DST_URI)
};
let logger_facility=
match env::var(derive_suffix(env_sufix, SYSLOG_LOGGER_LOG_FACILITY).as_ref())
{
Ok(val) =>
LogFacility::from_str(&val)?,
Err(VarError::NotPresent) =>
LogFacility::LOG_DAEMON,
Err(VarError::NotUnicode(_e)) =>
throw_error!("{} is not unicode", SYSLOG_LOGGER_LOG_FACILITY)
};
let logger_formatter =
match env::var(derive_suffix(env_sufix, SYSLOG_LOGGER_LOG_FORMATTER).as_ref())
{
Ok(val) =>
SyslogFormatters::from_str(&val)?,
Err(VarError::NotPresent) =>
DEFAULT_SYSLOG_FORMATTER_TYPE,
Err(VarError::NotUnicode(_e)) =>
throw_error!("{} is not unicode", SYSLOG_LOGGER_LOG_FORMATTER)
};
let logger_identity =
match env::var(derive_suffix(env_sufix, SYSLOG_LOGGER_LOG_IDENTITY).as_ref())
{
Ok(val) =>
Some(val),
Err(VarError::NotPresent) =>
None,
Err(VarError::NotUnicode(_e)) =>
throw_error!("{} is not unicode", SYSLOG_LOGGER_LOG_IDENTITY)
};
return Ok(
Some(
Self
{
logger_type_res, logger_facility, logger_formatter, logger_identity
}
)
);
}
}
#[derive(Debug, PartialEq, Eq)]
enum LoggerDst<'a>
{
LoLocal{ dst_path: Option<&'a str> },
#[cfg(feature = "build_ext_net")]
LoUdp{ dst_host: &'a str },
#[cfg(feature = "build_ext_net")]
LoTcp{ dst_host: &'a str },
#[cfg(feature = "build_ext_tls")]
LoTls
{
dst_host: &'a str,
serv_name: &'a str,
root_cert_path: &'a str,
},
#[cfg(feature = "build_ext_file")]
LoFile{ dst_path: &'a str }
}
impl<'a> LoggerDst<'a>
{
fn from_str(lo_sch: &'a str) -> SyRes<Self>
{
let (dst_type, payload) =
lo_sch
.split_once("#")
.ok_or_else(||
map_error!("{} incorrect format, expected type#dst:extras", SYSLOG_LOGGER_DST_URI)
)?;
let res =
match dst_type.to_lowercase().as_str()
{
LOGGER_TYPE_LOCAL =>
{
let dst_path = payload.trim();
if dst_path.is_empty() == false
{
Self::LoLocal{ dst_path: Some(dst_path) }
}
else
{
Self::LoLocal{ dst_path: None }
}
},
#[cfg(feature = "build_ext_net")]
LOGGER_TYPE_UDP =>
{
let dst_host = payload.trim();
Self::LoUdp{ dst_host }
},
#[cfg(not(feature = "build_ext_net"))]
LOGGER_TYPE_UDP =>
throw_error!("cannot use UDP, crate compiled without `build_ext_net`!"),
#[cfg(feature = "build_ext_net")]
LOGGER_TYPE_TCP => {
let dst_host = payload.trim();
Self::LoTcp{ dst_host }
},
#[cfg(not(feature = "build_ext_net"))]
LOGGER_TYPE_TCP =>
throw_error!("cannot use UDP, crate compiled without `build_ext_net`!"),
#[cfg(feature = "build_ext_tls")]
LOGGER_TYPE_TLS =>
{
let (dst_host, payload) =
payload
.split_once("#")
.map(|c| (c.0.trim(), c.1))
.ok_or_else(||
map_error!("SYSLOG_LOGGER_DST_URI incorrect format, expected type#dst:extras")
)?;
let (serv_name, root_cert_path) =
payload
.split_once("#")
.map(|c| (c.0.trim(), c.1))
.ok_or_else(||
map_error!("SYSLOG_LOGGER_DST_URI incorrect format, expected type#dst:extras")
)?;
Self::LoTls{ dst_host, serv_name, root_cert_path }
},
#[cfg(not(feature = "build_ext_tls"))]
LOGGER_TYPE_TLS =>
throw_error!("cannot use UDP, crate compiled without `build_ext_tls`!"),
#[cfg(feature = "build_ext_file")]
LOGGER_TYPE_FILE =>
{
let dst_path = payload.trim();
Self::LoFile{ dst_path }
},
#[cfg(not(feature = "build_ext_file"))]
LOGGER_TYPE_FILE =>
throw_error!("cannot use UDP, crate compiled without `build_ext_file`!"),
n =>
throw_error!("SYSLOG_LOGGER_DST_URI unknown syslog dest type: {}", n)
};
return Ok(res);
}
}
pub
fn init_event_log(env_suffix: Option<&str>) -> SyRes<()>
{
let Some(envs) = EnvVars::read_env(env_suffix)?
else { return Ok(()) };
let dst = LoggerDst::from_str(&envs.logger_type_res)?;
match dst
{
LoggerDst::LoLocal{ dst_path } =>
{
let tap =
if let Some(dst_path) = dst_path
{
SyslogLocal::new_custom_path(dst_path, false)?
}
else
{
SyslogLocal::new()
};
let syslog_inst =
match envs.logger_formatter
{
SyslogFormatters::FormatRfc3146 =>
{
openlog_with
::<FormatRfc3146, SyslogLocal>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatRfc5424 =>
{
openlog_with
::<FormatRfc5424, SyslogLocal>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatFile =>
{
openlog_with
::<FormatFile, SyslogLocal>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
};
set_logger(syslog_inst)?
},
#[cfg(feature = "build_ext_net")]
LoggerDst::LoUdp{ dst_host } =>
{
let tap = SyslogNetUdp::new(dst_host, None)?;
let syslog_inst =
match envs.logger_formatter
{
SyslogFormatters::FormatRfc3146 =>
{
openlog_with
::<FormatRfc3146, SyslogNetUdp>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatRfc5424 =>
{
openlog_with
::<FormatRfc5424, SyslogNetUdp>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatFile =>
{
openlog_with
::<FormatFile, SyslogNetUdp>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
}
};
LOGGER
.set(syslog_inst)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)?;
},
#[cfg(feature = "build_ext_net")]
LoggerDst::LoTcp{ dst_host } => {
let tap = SyslogNetTcp::new(dst_host, None, None)?;
let syslog_inst =
match envs.logger_formatter
{
SyslogFormatters::FormatRfc3146 =>
{
openlog_with
::<FormatRfc3146, SyslogNetTcp>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatRfc5424 =>
{
openlog_with
::<FormatRfc5424, SyslogNetTcp>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatFile =>
{
openlog_with
::<FormatFile, SyslogNetTcp>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
}
};
LOGGER
.set(syslog_inst)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)?;
},
#[cfg(feature = "build_ext_tls")]
LoggerDst::LoTls{ dst_host, serv_name, root_cert_path } =>
{
let tap = SyslogTls::new2(dst_host, None, serv_name, root_cert_path, None)?;
let syslog_inst =
match envs.logger_formatter
{
SyslogFormatters::FormatRfc3146 =>
{
openlog_with
::<FormatRfc3146, SyslogTls>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatRfc5424 =>
{
openlog_with
::<FormatRfc5424, SyslogTls>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID,envs. logger_facility, tap)?
},
SyslogFormatters::FormatFile =>
{
openlog_with
::<FormatFile, SyslogTls>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
}
};
LOGGER
.set(syslog_inst)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)?;
},
#[cfg(feature = "build_ext_file")]
LoggerDst::LoFile{ dst_path } =>
{
let tap = SyslogFile::new(dst_path);
let syslog_inst =
match envs.logger_formatter
{
SyslogFormatters::FormatRfc3146 =>
{
openlog_with
::<FormatRfc3146, SyslogFile>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatRfc5424 =>
{
openlog_with
::<FormatRfc5424, SyslogFile>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
},
SyslogFormatters::FormatFile =>
{
openlog_with
::<FormatFile, SyslogFile>(envs.logger_identity,
LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, envs.logger_facility, tap)?
}
};
LOGGER
.set(syslog_inst)
.map_err(|_e|
map_error!("syslog instance has been already initialized!")
)?;
}
}
return Ok(());
}
pub
fn logger_log(pri: Priority, msg: String)
{
let Some(logger) = LOGGER.get()
else { return; };
let _ = logger.syslog(pri, msg);
}
#[macro_export]
macro_rules! logger_dbg
{
($($arg:tt)*) => (
$crate::logger::logger_log($crate::Priority::LOG_DEBUG, format!($($arg)*))
)
}
#[macro_export]
macro_rules! logger_info
{
($($arg:tt)*) => (
$crate::logger::logger_log($crate::Priority::LOG_INFO, format!($($arg)*))
)
}
#[macro_export]
macro_rules! logger_error
{
($($arg:tt)*) => (
$crate::logger::logger_log($crate::Priority::LOG_ERR, format!($($arg)*))
)
}
#[macro_export]
macro_rules! logger_warn
{
($($arg:tt)*) => (
$crate::logger::logger_log($crate::Priority::LOG_WARNING, format!($($arg)*))
)
}
#[macro_export]
macro_rules! logger_emerg
{
($($arg:tt)*) => (
$crate::logger::logger_log($crate::Priority::LOG_EMERG, format!($($arg)*))
)
}
#[macro_export]
macro_rules! logger_custom
{
($prio:expr, $($arg:tt)*) => (
$crate::logger::logger_log($prio, format!($($arg)*))
)
}
#[test]
fn test_parsing_logger()
{
let res = LoggerDst::from_str("local#").unwrap();
assert_eq!(res, LoggerDst::LoLocal { dst_path: None });
let res = LoggerDst::from_str("local#/var/run/syslogcustom").unwrap();
assert_eq!(res, LoggerDst::LoLocal { dst_path: Some("/var/run/syslogcustom") });
#[cfg(feature = "build_ext_net")]
{
let res = LoggerDst::from_str("udp#127.0.0.1:80").unwrap();
assert_eq!(res, LoggerDst::LoUdp { dst_host: "127.0.0.1:80" });
let res = LoggerDst::from_str("udp#[fe00:::0]:80").unwrap();
assert_eq!(res, LoggerDst::LoUdp { dst_host: "[fe00:::0]:80" });
let res = LoggerDst::from_str("udp#www.mydomain.tdl:80").unwrap();
assert_eq!(res, LoggerDst::LoUdp { dst_host: "www.mydomain.tdl:80" });
let res = LoggerDst::from_str("tcp#127.0.0.1:80").unwrap();
assert_eq!(res, LoggerDst::LoTcp { dst_host: "127.0.0.1:80" });
let res = LoggerDst::from_str("tcp#[fe00:::0]:80").unwrap();
assert_eq!(res, LoggerDst::LoTcp { dst_host: "[fe00:::0]:80" });
let res = LoggerDst::from_str("tcp#www.mydomain.tdl:80").unwrap();
assert_eq!(res, LoggerDst::LoTcp { dst_host: "www.mydomain.tdl:80" });
}
#[cfg(feature = "build_ext_tls")]
{
let res = LoggerDst::from_str("tls#127.0.0.1:80#servername#/tmp/a1.crt").unwrap();
assert_eq!(res, LoggerDst::LoTls { dst_host: "127.0.0.1:80", serv_name: "servername", root_cert_path: "/tmp/a1.crt" });
let res = LoggerDst::from_str("tls#[fe00:::0]:80#servername#/tmp/a1.crt").unwrap();
assert_eq!(res, LoggerDst::LoTls { dst_host: "[fe00:::0]:80", serv_name: "servername", root_cert_path: "/tmp/a1.crt" });
let res = LoggerDst::from_str("tls#www.mydomain.tdl#servername#/tmp/a1.crt").unwrap();
assert_eq!(res, LoggerDst::LoTls { dst_host: "www.mydomain.tdl", serv_name: "servername", root_cert_path: "/tmp/a1.crt" });
}
#[cfg(feature = "build_ext_file")]
{
let res = LoggerDst::from_str("file#/tmp/file1.txt").unwrap();
assert_eq!(res, LoggerDst::LoFile { dst_path: "/tmp/file1.txt" });
}
}
#[test]
fn test_env_set()
{
unsafe { env::set_var(SYSLOG_LOGGER_DST_URI, "file#/tmp/test_log1.txt") };
unsafe { env::set_var(SYSLOG_LOGGER_LOG_FACILITY, "LOG_DAEMON") };
unsafe { env::set_var(SYSLOG_LOGGER_LOG_FORMATTER, "FILE") };
unsafe { env::set_var(SYSLOG_LOGGER_LOG_IDENTITY, "test_logger_file") };
let re = EnvVars::read_env(None).unwrap().unwrap();
assert_eq!(re.logger_type_res.as_str(), "file#/tmp/test_log1.txt");
assert_eq!(re.logger_facility, LogFacility::LOG_DAEMON);
assert_eq!(re.logger_formatter, SyslogFormatters::FormatFile);
assert_eq!(re.logger_identity.as_ref().map(|v| v.as_str()), Some("test_logger_file"));
}
#[test]
fn test_env_set_suffix()
{
let suf_xio = Some("XIO");
unsafe { env::set_var(derive_suffix(suf_xio, SYSLOG_LOGGER_DST_URI).as_ref(), "file#/tmp/xio_out.txt") };
unsafe { env::set_var(derive_suffix(suf_xio, SYSLOG_LOGGER_LOG_FACILITY).as_ref(), "LOG_DAEMON") };
unsafe { env::set_var(derive_suffix(suf_xio, SYSLOG_LOGGER_LOG_FORMATTER).as_ref(), "FILE") };
unsafe { env::set_var(derive_suffix(suf_xio, SYSLOG_LOGGER_LOG_IDENTITY).as_ref(), "xio_rs") };
let suf_so9 = Some("SOCKET9");
unsafe { env::set_var(derive_suffix(suf_so9, SYSLOG_LOGGER_DST_URI).as_ref(), "file#/tmp/socket9_out.txt") };
unsafe { env::set_var(derive_suffix(suf_so9, SYSLOG_LOGGER_LOG_FACILITY).as_ref(), "LOG_DAEMON") };
unsafe { env::set_var(derive_suffix(suf_so9, SYSLOG_LOGGER_LOG_FORMATTER).as_ref(), "FILE") };
unsafe { env::set_var(derive_suffix(suf_so9, SYSLOG_LOGGER_LOG_IDENTITY).as_ref(), "socket9") };
let re = EnvVars::read_env(suf_so9).unwrap().unwrap();
assert_eq!(re.logger_type_res.as_str(), "file#/tmp/socket9_out.txt");
assert_eq!(re.logger_facility, LogFacility::LOG_DAEMON);
assert_eq!(re.logger_formatter, SyslogFormatters::FormatFile);
assert_eq!(re.logger_identity.as_ref().map(|v| v.as_str()), Some("socket9"));
}
#[test]
fn test_env_set2()
{
unsafe { env::remove_var(SYSLOG_LOGGER_DST_URI) };
unsafe { env::set_var(SYSLOG_LOGGER_LOG_FACILITY, "LOG_DAEMON") };
unsafe { env::set_var(SYSLOG_LOGGER_LOG_FORMATTER, "FILE") };
unsafe { env::set_var(SYSLOG_LOGGER_LOG_IDENTITY, "test_logger_file") };
let re = EnvVars::read_env(None).unwrap();
assert_eq!(re.is_none(), true);
}