use std::{io, os::raw::c_int};
use crate::{
formatter::{Formatter, FormatterContext, FullFormatter},
sink::{GetSinkProp, Sink, SinkProp},
sync::*,
Error, ErrorHandler, Level, LevelFilter, Record, Result, StdResult, StringBuf,
};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
enum SyslogLevel {
_Emerg = 0,
_Alert = 1,
Crit = 2,
Err = 3,
Warning = 4,
_Notice = 5,
Info = 6,
Debug = 7,
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct SyslogLevels([SyslogLevel; Level::count()]);
impl SyslogLevels {
#[must_use]
const fn new() -> Self {
Self([
SyslogLevel::Crit, SyslogLevel::Err, SyslogLevel::Warning, SyslogLevel::Info, SyslogLevel::Debug, SyslogLevel::Debug, ])
}
#[must_use]
fn level(&self, level: Level) -> SyslogLevel {
self.0[level as usize]
}
}
impl Default for SyslogLevels {
fn default() -> Self {
Self::new()
}
}
fn journal_send(args: impl Iterator<Item = impl AsRef<str>>) -> StdResult<(), io::Error> {
#[cfg(not(doc))] use libsystemd_sys::{const_iovec, journal as ffi};
let iovecs: Vec<_> = args.map(|a| unsafe { const_iovec::from_str(a) }).collect();
let result = unsafe { ffi::sd_journal_sendv(iovecs.as_ptr(), iovecs.len() as c_int) };
if result == 0 {
Ok(())
} else {
Err(io::Error::from_raw_os_error(result))
}
}
pub struct JournaldSink {
prop: SinkProp,
}
impl JournaldSink {
const SYSLOG_LEVELS: SyslogLevels = SyslogLevels::new();
#[must_use]
pub fn builder() -> JournaldSinkBuilder {
let prop = SinkProp::default();
prop.set_formatter(
FullFormatter::builder()
.time(false)
.source_location(false)
.build(),
);
JournaldSinkBuilder { prop }
}
}
impl GetSinkProp for JournaldSink {
fn prop(&self) -> &SinkProp {
&self.prop
}
}
impl Sink for JournaldSink {
fn log(&self, record: &Record) -> Result<()> {
let mut string_buf = StringBuf::new();
let mut ctx = FormatterContext::new();
self.prop
.formatter()
.format(record, &mut string_buf, &mut ctx)?;
let kvs = [
format!("MESSAGE={string_buf}"),
format!(
"PRIORITY={}",
JournaldSink::SYSLOG_LEVELS.level(record.level()) as u32
),
format!("TID={}", record.tid()),
];
let srcloc_kvs = match record.source_location() {
Some(srcloc) => [
Some(format!("CODE_FILE={}", srcloc.file_name())),
Some(format!("CODE_LINE={}", srcloc.line())),
],
None => [None, None],
};
journal_send(kvs.iter().chain(srcloc_kvs.iter().flatten())).map_err(Error::WriteRecord)
}
fn flush(&self) -> Result<()> {
Ok(())
}
}
#[allow(missing_docs)]
pub struct JournaldSinkBuilder {
prop: SinkProp,
}
impl JournaldSinkBuilder {
#[must_use]
pub fn level_filter(self, level_filter: LevelFilter) -> Self {
self.prop.set_level_filter(level_filter);
self
}
#[must_use]
pub fn formatter<F>(self, formatter: F) -> Self
where
F: Formatter + 'static,
{
self.prop.set_formatter(formatter);
self
}
#[must_use]
pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
self.prop.set_error_handler(handler);
self
}
pub fn build(self) -> Result<JournaldSink> {
let sink = JournaldSink { prop: self.prop };
Ok(sink)
}
pub fn build_arc(self) -> Result<Arc<JournaldSink>> {
self.build().map(Arc::new)
}
}