use core::fmt::{self, Write};
use std::ffi::CStr;
use log::{Level, Log, Metadata};
use crate::system::syslog;
pub struct Syslog;
const LIMIT: usize = 960;
const DOTDOTDOT_START: &[u8] = b"[...] ";
const DOTDOTDOT_END: &[u8] = b" [...]";
const NULL_BYTE: usize = 1; // for C string compatibility
const BUFSZ: usize = LIMIT + DOTDOTDOT_END.len() + NULL_BYTE;
const FACILITY: libc::c_int = libc::LOG_AUTH;
struct SysLogWriter {
buffer: [u8; BUFSZ],
cursor: usize,
facility: libc::c_int,
priority: libc::c_int,
}
impl SysLogWriter {
fn new(priority: libc::c_int, facility: libc::c_int) -> Self {
Self {
buffer: [0; BUFSZ],
cursor: 0,
priority,
facility,
}
}
fn append(&mut self, bytes: &[u8]) {
let num_bytes = bytes.len();
self.buffer[self.cursor..self.cursor + num_bytes].copy_from_slice(bytes);
self.cursor += num_bytes;
}
fn send_to_syslog(&mut self) {
self.append(&[0]);
let message = CStr::from_bytes_with_nul(&self.buffer[..self.cursor]).unwrap();
syslog(self.priority, self.facility, message);
self.cursor = 0;
}
}
impl Write for SysLogWriter {
fn write_str(&mut self, mut message: &str) -> fmt::Result {
loop {
if self.cursor + message.len() > LIMIT {
// floor_char_boundary is currently unstable
let mut truncate_boundary = LIMIT - self.cursor;
while !message.is_char_boundary(truncate_boundary) {
truncate_boundary -= 1;
}
truncate_boundary = message[..truncate_boundary]
.rfind(|c: char| c.is_ascii_whitespace())
.unwrap_or(truncate_boundary);
let left = &message[..truncate_boundary];
let right = &message[truncate_boundary..];
self.append(left.as_bytes());
self.append(DOTDOTDOT_END);
self.send_to_syslog();
self.append(DOTDOTDOT_START);
message = right;
} else {
self.append(message.as_bytes());
break;
}
}
Ok(())
}
}
impl Log for Syslog {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= log::max_level() && metadata.level() <= log::STATIC_MAX_LEVEL
}
fn log(&self, record: &log::Record) {
let priority = match record.level() {
Level::Error => libc::LOG_ERR,
Level::Warn => libc::LOG_WARNING,
Level::Info => libc::LOG_INFO,
Level::Debug => libc::LOG_DEBUG,
Level::Trace => libc::LOG_DEBUG,
};
let mut writer = SysLogWriter::new(priority, FACILITY);
let _ = write!(writer, "{}", record.args());
writer.send_to_syslog();
}
fn flush(&self) {
// pass
}
}
#[cfg(test)]
mod tests {
use log::Log;
use std::fmt::Write;
use super::{SysLogWriter, Syslog, FACILITY};
#[test]
fn can_write_to_syslog() {
let logger = Syslog;
let record = log::Record::builder()
.args(format_args!("Hello World!"))
.level(log::Level::Info)
.build();
logger.log(&record);
}
#[test]
fn can_handle_multiple_writes() {
let mut writer = SysLogWriter::new(libc::LOG_DEBUG, FACILITY);
for i in 1..20 {
let _ = write!(writer, "{}", "Test 123 ".repeat(i));
}
}
#[test]
fn can_truncate_syslog() {
let logger = Syslog;
let record = log::Record::builder()
.args(format_args!("This is supposed to be a very long syslog message but idk what to write, so I am just going to tell you about the time I tried to make coffee with a teapot. So I woke up one morning and decided to make myself a pot of coffee, however after all the wild coffee parties and mishaps the coffee pot had evetually given it's last cup on a tragic morning I call wednsday. So it came to, that the only object capable of giving me hope for the day was my teapot. As I stood in the kitchen and reached for my teapot it, as if sensing the impending horrors that awaited the innocent little teapot, emmited a horse sheak of desperation. \"three hundred and seven\", it said. \"What?\" I asked with a voice of someone who clearly did not want to be bothered until he had his daily almost medically necessary dose of caffine. \"I am a teapot\" it responded with a voice of increasing forcefulness. \"I am a teapot, not a coffee pot\". It was then, in my moments of confusion that my brain finally understood, this was a teapot."))
.level(log::Level::Info)
.build();
logger.log(&record);
}
#[test]
fn can_truncate_syslog_with_no_spaces() {
let logger = Syslog;
let record = log::Record::builder()
.args(format_args!("iwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercasesiwillhandlecornercases"))
.level(log::Level::Info)
.build();
logger.log(&record);
}
}