use std::io::{Error as IoError, ErrorKind, Result as IoResult, Write};
use crate::{DeferredNow, FormatFunction};
use super::{LevelToSyslogSeverity, SyslogFacility};
pub enum SyslogLineHeader {
Rfc5424(String),
Rfc3164,
}
pub(crate) struct LineWriter {
header: SyslogLineHeader,
hostname: String,
process: String,
pid: u32,
format: FormatFunction,
determine_severity: LevelToSyslogSeverity,
facility: SyslogFacility,
}
impl LineWriter {
pub(crate) fn new(
header: SyslogLineHeader,
determine_severity: LevelToSyslogSeverity,
facility: SyslogFacility,
process: String,
pid: u32,
format: FormatFunction,
) -> IoResult<LineWriter> {
const UNKNOWN_HOSTNAME: &str = "<unknown_hostname>";
Ok(LineWriter {
header,
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(),
)
})
},
)?,
process,
pid,
format,
determine_severity,
facility,
})
}
pub(crate) fn write_syslog_entry(
&self,
buffer: &mut dyn Write,
now: &mut DeferredNow,
record: &log::Record,
) -> IoResult<()> {
let severity = (self.determine_severity)(record.level());
match self.header {
SyslogLineHeader::Rfc3164 => {
write!(
buffer,
"<{pri}>{timestamp} {tag}[{procid}]: ",
pri = self.facility as u8 | severity as u8,
timestamp = now.format_rfc3164(),
tag = self.process,
procid = self.pid
)?;
(self.format)(buffer, now, record)?;
}
SyslogLineHeader::Rfc5424(ref message_id) => {
#[allow(clippy::write_literal)]
write!(
buffer,
"<{pri}>{version} {timestamp} {hostname} {appname} {procid} {msgid} ",
pri = self.facility as u8 | severity as u8,
version = "1",
timestamp = now.format_rfc3339(),
hostname = self.hostname,
appname = self.process,
procid = self.pid,
msgid = message_id,
)?;
write_key_value_pairs(buffer, record)?;
(self.format)(buffer, now, record)?;
}
}
Ok(())
}
}
fn write_key_value_pairs(
w: &mut dyn std::io::Write,
record: &log::Record<'_>,
) -> Result<(), std::io::Error> {
let mut kv_written = false;
#[cfg(feature = "kv")]
if record.key_values().count() > 0 {
write!(w, "[log_kv ",)?;
let mut kv_stream = KvStream(w, false);
record.key_values().visit(&mut kv_stream).ok();
write!(w, "] ")?;
kv_written = true;
}
if !kv_written {
write!(w, "- ")?;
}
Ok(())
}
#[cfg(feature = "kv")]
struct KvStream<'a>(&'a mut dyn std::io::Write, bool);
#[cfg(feature = "kv")]
impl<'kvs, 'a> log::kv::VisitSource<'kvs> for KvStream<'a>
where
'kvs: 'a,
{
fn visit_pair(
&mut self,
key: log::kv::Key<'kvs>,
value: log::kv::Value<'kvs>,
) -> Result<(), log::kv::Error> {
if self.1 {
write!(self.0, " ")?;
}
write!(self.0, "{key}=\"{value:?}\"")?;
self.1 = true;
Ok(())
}
}