use std::fmt;
use std::fmt::Formatter;
use jiff::Timestamp;
use jiff::Zoned;
use crate::internal::hostname;
use crate::Facility;
use crate::SDElement;
use crate::Severity;
const NILVALUE: &str = "-";
#[derive(Debug, Clone)]
pub struct SyslogContext {
facility: Facility,
hostname: Option<String>,
appname: Option<String>,
procid: Option<String>,
}
impl Default for SyslogContext {
fn default() -> Self {
Self::new()
}
}
impl SyslogContext {
pub const fn const_new() -> Self {
Self {
facility: Facility::USER,
hostname: None,
appname: None,
procid: None,
}
}
pub fn new() -> Self {
let procid = std::process::id();
let appname = std::env::current_exe().ok().and_then(|path| {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| name.to_string())
});
let hostname = hostname().and_then(|name| name.to_str().map(|name| name.to_string()));
Self {
facility: Facility::USER,
hostname,
appname,
procid: Some(procid.to_string()),
}
}
pub fn facility(&mut self, facility: Facility) -> &mut Self {
self.facility = facility;
self
}
pub fn hostname(&mut self, hostname: impl Into<String>) -> &mut Self {
self.hostname = Some(hostname.into());
self
}
pub fn appname(&mut self, appname: impl Into<String>) -> &mut Self {
self.appname = Some(appname.into());
self
}
pub fn procid(&mut self, procid: impl Into<String>) -> &mut Self {
self.procid = Some(procid.into());
self
}
pub fn format_rfc3164<M>(&self, severity: Severity, message: Option<M>) -> RFC3164Formatter<M> {
RFC3164Formatter {
context: self,
severity,
message,
}
}
pub fn format_rfc5424<S, M>(
&self,
severity: Severity,
msgid: Option<S>,
elements: Vec<SDElement>,
message: Option<M>,
) -> RFC5424Formatter<M>
where
S: Into<String>,
M: fmt::Display,
{
let msgid = msgid.map(|s| s.into());
RFC5424Formatter {
context: self,
severity,
msgid,
elements,
message,
}
}
}
fn nullable_value(value: Option<&str>) -> &str {
value.unwrap_or(NILVALUE)
}
#[derive(Debug)]
pub struct RFC3164Formatter<'a, M> {
context: &'a SyslogContext,
severity: Severity,
message: Option<M>,
}
impl<M> fmt::Display for RFC3164Formatter<'_, M>
where
M: fmt::Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let pri = (self.context.facility.code() << 3) | self.severity.code();
let ts = Zoned::now().strftime("%b %e %T");
let hostname = nullable_value(self.context.hostname.as_deref());
let appname = nullable_value(self.context.appname.as_deref());
write!(f, "<{pri}>{ts} {hostname} {appname}")?;
if let Some(procid) = &self.context.procid {
write!(f, "[{procid}]")?;
}
if let Some(message) = &self.message {
write!(f, ": {message}")?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct RFC5424Formatter<'a, M> {
context: &'a SyslogContext,
severity: Severity,
msgid: Option<String>,
elements: Vec<SDElement>,
message: Option<M>,
}
impl<M> fmt::Display for RFC5424Formatter<'_, M>
where
M: fmt::Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let pri = (self.context.facility.code() << 3) | self.severity.code();
let ver = 1;
let ts = Timestamp::now();
let hostname = nullable_value(self.context.hostname.as_deref());
let appname = nullable_value(self.context.appname.as_deref());
let procid = nullable_value(self.context.procid.as_deref());
let msgid = nullable_value(self.msgid.as_deref());
write!(
f,
"<{pri}>{ver} {ts:.6} {hostname} {appname} {procid} {msgid} "
)?;
if self.elements.is_empty() {
write!(f, "-")?;
} else {
for element in &self.elements {
write!(f, "{element}")?;
}
}
if let Some(message) = &self.message {
write!(f, " {message}")?;
}
Ok(())
}
}