#![cfg(unix)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::io::Write;
use std::os::unix::net::UnixDatagram;
use logforth_core::Append;
use logforth_core::Diagnostic;
use logforth_core::Error;
use logforth_core::kv::Key;
use logforth_core::kv::Value;
use logforth_core::kv::Visitor;
use logforth_core::record::Level;
use logforth_core::record::Record;
mod field;
#[cfg(target_os = "linux")]
mod memfd;
const JOURNALD_PATH: &str = "/run/systemd/journal/socket";
fn current_exe_identifier() -> Option<String> {
let executable = std::env::current_exe().ok()?;
Some(executable.file_name()?.to_string_lossy().into_owned())
}
#[derive(Debug)]
pub struct Journald {
socket: UnixDatagram,
extra_fields: Vec<u8>,
syslog_identifier: String,
}
impl Journald {
pub fn new() -> Result<Self, Error> {
let socket = UnixDatagram::unbound().map_err(Error::from_io_error)?;
let sub = Self {
socket,
extra_fields: Vec::new(),
syslog_identifier: current_exe_identifier().unwrap_or_default(),
};
sub.send_payload(&[])?;
Ok(sub)
}
pub fn with_extra_field<K: AsRef<str>, V: AsRef<[u8]>>(mut self, name: K, value: V) -> Self {
field::put_field_bytes(
&mut self.extra_fields,
field::FieldName::WriteEscaped(name.as_ref()),
value.as_ref(),
);
self
}
pub fn with_extra_fields<I, K, V>(mut self, extra_fields: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<[u8]>,
{
for (name, value) in extra_fields {
field::put_field_bytes(
&mut self.extra_fields,
field::FieldName::WriteEscaped(name.as_ref()),
value.as_ref(),
);
}
self
}
pub fn with_syslog_identifier(mut self, identifier: String) -> Self {
self.syslog_identifier = identifier;
self
}
pub fn syslog_identifier(&self) -> &str {
&self.syslog_identifier
}
fn send_payload(&self, payload: &[u8]) -> Result<usize, Error> {
self.socket.send_to(payload, JOURNALD_PATH).or_else(|err| {
if Some(libc::EMSGSIZE) == err.raw_os_error() {
self.send_large_payload(payload)
} else {
Err(Error::from_io_error(err))
}
})
}
#[cfg(all(unix, not(target_os = "linux")))]
fn send_large_payload(&self, _payload: &[u8]) -> Result<usize, Error> {
Err(Error::new("large payloads not supported on non-Linux OS"))
}
#[cfg(target_os = "linux")]
fn send_large_payload(&self, payload: &[u8]) -> Result<usize, Error> {
memfd::send_large_payload(&self.socket, payload)
.map_err(|err| Error::new("failed to send payload via memfd").set_source(err))
}
}
struct WriteKeyValues<'a>(&'a mut Vec<u8>);
impl Visitor for WriteKeyValues<'_> {
fn visit(&mut self, key: Key, value: Value) -> Result<(), Error> {
let key = key.as_str();
field::put_field_length_encoded(self.0, field::FieldName::WriteEscaped(key), value);
Ok(())
}
}
impl Append for Journald {
fn append(&self, record: &Record, diags: &[Box<dyn Diagnostic>]) -> Result<(), Error> {
use field::*;
let mut buffer = vec![];
let priority = match record.level() {
Level::Fatal | Level::Fatal2 | Level::Fatal3 | Level::Fatal4 => b"0", Level::Error3 | Level::Error4 => b"1", Level::Error2 => b"2", Level::Error => b"3", Level::Warn | Level::Warn2 | Level::Warn3 | Level::Warn4 => b"4", Level::Info2 | Level::Info3 | Level::Info4 => b"5", Level::Info => b"6", Level::Debug
| Level::Debug2
| Level::Debug3
| Level::Debug4
| Level::Trace
| Level::Trace2
| Level::Trace3
| Level::Trace4 => b"7", };
put_field_bytes(&mut buffer, FieldName::WellFormed("PRIORITY"), priority);
put_field_length_encoded(
&mut buffer,
FieldName::WellFormed("MESSAGE"),
record.payload(),
);
writeln!(&mut buffer, "SYSLOG_PID={}", std::process::id()).unwrap();
if !self.syslog_identifier.is_empty() {
put_field_bytes(
&mut buffer,
FieldName::WellFormed("SYSLOG_IDENTIFIER"),
self.syslog_identifier.as_bytes(),
);
}
if let Some(file) = record.file() {
put_field_bytes(
&mut buffer,
FieldName::WellFormed("CODE_FILE"),
file.as_bytes(),
);
}
if let Some(module) = record.module_path() {
put_field_bytes(
&mut buffer,
FieldName::WellFormed("CODE_MODULE"),
module.as_bytes(),
);
}
if let Some(line) = record.line() {
writeln!(&mut buffer, "CODE_LINE={line}").unwrap();
}
put_field_bytes(
&mut buffer,
FieldName::WellFormed("TARGET"),
record.target().as_bytes(),
);
let mut visitor = WriteKeyValues(&mut buffer);
record.key_values().visit(&mut visitor)?;
for d in diags {
d.visit(&mut visitor)?;
}
buffer.extend_from_slice(&self.extra_fields);
self.send_payload(&buffer)?;
Ok(())
}
fn flush(&self) -> Result<(), Error> {
Ok(())
}
}