use btoi::btoi;
use memchr::arch::all::is_equal;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum LogLevel {
Emergent = 0,
Alert = 1,
Crit = 2,
Err = 3,
Warn = 4,
Notice = 5,
Info = 6,
Debug = 7,
}
impl LogLevel {
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn as_bytes(self) -> &'static [u8] {
match self {
Self::Emergent => b"emerg",
Self::Alert => b"alert",
Self::Crit => b"crit",
Self::Err => b"error",
Self::Warn => b"warn",
Self::Notice => b"notice",
Self::Info => b"info",
Self::Debug => b"debug",
}
}
}
impl From<u8> for LogLevel {
fn from(level: u8) -> Self {
let level = level.clamp(Self::Emergent.as_u8(), Self::Debug.as_u8());
if level == Self::Emergent.as_u8() {
Self::Emergent
} else if level == Self::Alert.as_u8() {
Self::Alert
} else if level == Self::Crit.as_u8() {
Self::Crit
} else if level == Self::Err.as_u8() {
Self::Err
} else if level == Self::Warn.as_u8() {
Self::Warn
} else if level == Self::Notice.as_u8() {
Self::Notice
} else if level == Self::Info.as_u8() {
Self::Info
} else {
Self::Debug
}
}
}
impl From<i64> for LogLevel {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
fn from(level: i64) -> Self {
(level.clamp(Self::Emergent.as_u8().into(), Self::Debug.as_u8().into()) as u8).into()
}
}
pub fn parse_loglevel(level: &[u8], default: LogLevel) -> LogLevel {
let level = level.trim_ascii();
if level.is_empty() {
default
} else if let Ok(level) = btoi::<i64>(level) {
level.into()
} else if is_equal(level, b"emerg") {
LogLevel::Emergent
} else if is_equal(level, b"alert") {
LogLevel::Alert
} else if is_equal(level, b"crit") {
LogLevel::Crit
} else if is_equal(level, b"error") {
LogLevel::Err
} else if is_equal(level, b"warn") {
LogLevel::Warn
} else if is_equal(level, b"notice") {
LogLevel::Notice
} else if is_equal(level, b"info") {
LogLevel::Info
} else if is_equal(level, b"debug") {
LogLevel::Debug
} else {
default
}
}
pub const SYSLOG_ACTION_CLOSE: libc::c_int = 0;
pub const SYSLOG_ACTION_OPEN: libc::c_int = 1;
pub const SYSLOG_ACTION_READ: libc::c_int = 2;
pub const SYSLOG_ACTION_READ_ALL: libc::c_int = 3;
pub const SYSLOG_ACTION_READ_CLEAR: libc::c_int = 4;
pub const SYSLOG_ACTION_CLEAR: libc::c_int = 5;
pub const SYSLOG_ACTION_CONSOLE_OFF: libc::c_int = 6;
pub const SYSLOG_ACTION_CONSOLE_ON: libc::c_int = 7;
pub const SYSLOG_ACTION_CONSOLE_LEVEL: libc::c_int = 8;
pub const SYSLOG_ACTION_SIZE_UNREAD: libc::c_int = 9;
pub const SYSLOG_ACTION_SIZE_BUFFER: libc::c_int = 10;
#[cfg(feature = "log")]
mod syslog_enabled {
use std::{
io::Write,
mem::MaybeUninit,
os::fd::BorrowedFd,
sync::{
atomic::{AtomicBool, AtomicU8, Ordering},
OnceLock,
},
};
use nix::{
errno::Errno,
time::{clock_gettime, ClockId},
};
use ringbuf::{
storage::{Array, Heap},
traits::*,
Arc, SharedRb,
};
use crate::{
config::SYSLOG_STACK_SIZE,
log::{emit_json_to_fd, global_log_rlimit, LogMap, LogValue, WriteBuf},
syslog::*,
};
enum SyslogRb {
Heap(Arc<SharedRb<Heap<u8>>>),
Static(Arc<SharedRb<Array<u8, SYSLOG_STACK_SIZE>>>),
}
macro_rules! with_syslog_ring {
($self:expr, | $r:ident | $body:expr) => {
match $self {
SyslogRb::Heap(ref ring) => {
let $r = ring.as_ref();
$body
}
SyslogRb::Static(ref ring) => {
let $r = ring.as_ref();
$body
}
}
};
}
pub struct Syslog {
ring: SyslogRb,
locked: AtomicBool,
level: AtomicU8,
}
unsafe impl Sync for Syslog {}
impl Syslog {
pub fn new(cap: usize, level: LogLevel, use_stack: bool) -> Self {
let ring = if use_stack {
SyslogRb::Static(Arc::new(SharedRb::<Array<u8, SYSLOG_STACK_SIZE>>::default()))
} else {
SyslogRb::Heap(Arc::new(SharedRb::<Heap<u8>>::new(cap)))
};
Syslog {
ring,
locked: AtomicBool::new(false),
level: AtomicU8::new(level as u8),
}
}
pub fn write_log(&self, level: LogLevel, msg: &LogMap, tty: bool, msg_hash: u64) {
if level.as_u8() > self.loglevel() {
return;
}
let rlimit = global_log_rlimit();
let (allowed, suppressed) = rlimit.should_log(msg_hash);
if suppressed > 0 {
self.emit_suppressed_summary(level, suppressed, tty);
}
if !allowed {
return;
}
if let Some(fd) = Self::logfd() {
emit_json_to_fd(fd, msg, tty, level);
}
if self.is_locked() {
return;
}
self.emit_to_ring(level, msg);
}
fn emit_suppressed_summary(&self, level: LogLevel, count: u32, tty: bool) {
let mut msg = LogMap::new();
let _ = msg.try_insert("ctx", LogValue::Borrowed("log"));
let _ = msg.try_insert("op", LogValue::Borrowed("rlimit"));
let _ = msg.try_insert("suppressed", LogValue::from(count));
if let Some(fd) = Self::logfd() {
emit_json_to_fd(fd, &msg, tty, level);
}
if !self.is_locked() {
self.emit_to_ring(level, &msg);
}
}
fn emit_to_ring(&self, level: LogLevel, msg: &LogMap) {
let mut ring_buf = WriteBuf::new();
if msg.write_json(&mut ring_buf).is_ok() {
let msg_str = unsafe { std::str::from_utf8_unchecked(&ring_buf.0) };
if let Ok(ring_msg) = self.try_format_ring_msg(level, msg_str) {
let _ = self.push_slice_overwrite(ring_msg.as_bytes());
}
}
}
#[expect(clippy::type_complexity)]
pub fn syslog(
&self,
action: libc::c_int,
len: usize,
) -> Result<(usize, Option<Vec<u8>>), Errno> {
if self.is_locked() {
return Err(Errno::EPERM);
}
match action {
SYSLOG_ACTION_CLOSE | SYSLOG_ACTION_OPEN => Ok((0, None)),
SYSLOG_ACTION_READ => {
if len == 0 {
return Ok((0, None));
}
self.read_and_consume(len)
}
SYSLOG_ACTION_READ_ALL => {
if len == 0 {
return Ok((0, None));
}
self.read_all_no_consume(len)
}
SYSLOG_ACTION_READ_CLEAR => {
if len == 0 {
return Ok((0, None));
}
let (count, data_vec) = self.read_all_no_consume(len)?;
if count > 0 {
self.pop_count(count);
}
Ok((count, data_vec))
}
SYSLOG_ACTION_CLEAR => {
self.skip_all();
Ok((0, None))
}
SYSLOG_ACTION_CONSOLE_OFF => {
self.set_loglevel(LogLevel::Emergent.as_u8());
Ok((0, None))
}
SYSLOG_ACTION_CONSOLE_ON => {
self.set_loglevel(LogLevel::Warn.as_u8());
Ok((0, None))
}
SYSLOG_ACTION_CONSOLE_LEVEL => {
let level: u8 = len.try_into().or(Err(Errno::EINVAL))?;
if !(1..=8).contains(&level) {
return Err(Errno::EINVAL);
}
self.set_loglevel(level);
Ok((0, None))
}
SYSLOG_ACTION_SIZE_UNREAD => {
let unread = self.ring_unread();
Ok((unread, None))
}
SYSLOG_ACTION_SIZE_BUFFER => {
let cap = self.ring_capacity();
Ok((cap, None))
}
_ => Err(Errno::EINVAL),
}
}
pub fn logfd() -> Option<BorrowedFd<'static>> {
crate::log::log_fd()
}
pub fn loglevel(&self) -> u8 {
self.level.load(Ordering::SeqCst)
}
pub fn set_loglevel(&self, level: u8) {
let lv = level.clamp(LogLevel::Emergent.as_u8(), LogLevel::Debug.as_u8());
self.level.store(lv, Ordering::SeqCst);
}
pub fn is_locked(&self) -> bool {
self.locked.load(Ordering::SeqCst)
}
pub fn lock(&self) -> bool {
if self
.locked
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
self.skip_all();
true
} else {
false
}
}
fn try_format_ring_msg(&self, level: LogLevel, msg: &str) -> Result<String, Errno> {
#[expect(clippy::cast_precision_loss)]
let now = match clock_gettime(ClockId::CLOCK_BOOTTIME) {
Ok(ts) => ts.tv_sec() as f64 + (ts.tv_nsec() as f64 / 1_000_000_000.0),
Err(_) => 0.0,
};
let mut buf = WriteBuf::new();
writeln!(buf, "<{}>[{:12.6}] {}", level.as_u8(), now, msg).or(Err(Errno::ENOMEM))?;
Ok(unsafe { String::from_utf8_unchecked(buf.into_inner()) })
}
#[expect(clippy::type_complexity)]
fn read_and_consume(&self, len: usize) -> Result<(usize, Option<Vec<u8>>), Errno> {
let (count, out) = self.peek_and_copy(len)?;
if count == 0 {
return Ok((0, None));
}
self.pop_count(count);
Ok((count, Some(out)))
}
#[expect(clippy::type_complexity)]
fn read_all_no_consume(&self, len: usize) -> Result<(usize, Option<Vec<u8>>), Errno> {
let (count, out) = self.peek_and_copy(len)?;
if count == 0 {
Ok((0, None))
} else {
Ok((count, Some(out)))
}
}
fn skip_all(&self) {
with_syslog_ring!(&self.ring, |ring| {
let n = ring.occupied_len();
if n > 0 {
unsafe { ring.advance_read_index(n) };
}
});
}
fn pop_count(&self, count: usize) {
if count == 0 {
return;
}
with_syslog_ring!(&self.ring, |ring| {
let n = count.min(ring.occupied_len());
if n > 0 {
unsafe { ring.advance_read_index(n) };
}
});
}
fn peek_and_copy(&self, len: usize) -> Result<(usize, Vec<u8>), Errno> {
with_syslog_ring!(&self.ring, |ring| {
let rlen = ring.occupied_len().min(len);
if rlen == 0 {
return Ok((0, Vec::new()));
}
let (left, right) = ring.occupied_slices();
let mut out = Vec::new();
out.try_reserve(rlen).or(Err(Errno::ENOMEM))?;
let left_n = rlen.min(left.len());
for item in left.iter().take(left_n) {
out.push(unsafe { item.assume_init() });
}
let right_n = rlen.checked_sub(left_n).ok_or(Errno::EOVERFLOW)?;
for item in right.iter().take(right_n) {
out.push(unsafe { item.assume_init() });
}
Ok((out.len(), out))
})
}
fn push_slice_overwrite(&self, data: &[u8]) -> Result<(), Errno> {
with_syslog_ring!(&self.ring, |ring| {
let cap = ring.capacity().get();
let occ = ring.occupied_len();
let vacant = cap.checked_sub(occ).ok_or(Errno::EOVERFLOW)?;
let evict = if data.len() > vacant {
data.len().checked_sub(vacant).ok_or(Errno::EOVERFLOW)?
} else {
0
};
if evict > 0 {
let n = evict.min(occ);
unsafe { ring.advance_read_index(n) };
}
let data = if data.len() > cap {
let off = data.len().checked_sub(cap).ok_or(Errno::EOVERFLOW)?;
&data[off..]
} else {
data
};
let wi = ring.write_index();
let ri = ring.read_index();
let end = ri.checked_add(cap).ok_or(Errno::EOVERFLOW)?;
let (left, right) = unsafe { ring.unsafe_slices_mut(wi, end) };
let left_n = data.len().min(left.len());
left[..left_n].copy_from_slice(unsafe {
&*(std::ptr::from_ref::<[u8]>(&data[..left_n]) as *const [MaybeUninit<u8>])
});
let right_n = data.len().checked_sub(left_n).ok_or(Errno::EOVERFLOW)?;
if right_n > 0 {
right[..right_n].copy_from_slice(unsafe {
&*(std::ptr::from_ref::<[u8]>(&data[left_n..])
as *const [MaybeUninit<u8>])
});
}
let written = left_n.checked_add(right_n).ok_or(Errno::EOVERFLOW)?;
unsafe { ring.advance_write_index(written) };
Ok(())
})
}
fn ring_unread(&self) -> usize {
with_syslog_ring!(&self.ring, |ring| ring.occupied_len())
}
fn ring_capacity(&self) -> usize {
with_syslog_ring!(&self.ring, |ring| ring.capacity().get())
}
}
pub static SYSLOG_INSTANCE: OnceLock<Syslog> = OnceLock::new();
pub fn init_global_syslog(cap: usize, level: LogLevel, use_stack: bool) -> Result<(), Errno> {
if !use_stack && cap == 0 {
return Err(Errno::EINVAL);
}
SYSLOG_INSTANCE
.set(Syslog::new(cap, level, use_stack))
.or(Err(Errno::EAGAIN))
}
pub fn global_syslog() -> Option<&'static Syslog> {
SYSLOG_INSTANCE.get()
}
}
#[cfg(feature = "log")]
pub use syslog_enabled::*;
#[cfg(feature = "log")]
#[inline(always)]
pub fn current_loglevel() -> u8 {
global_syslog().map(|sys| sys.loglevel()).unwrap_or(4)
}
#[cfg(not(feature = "log"))]
#[inline(always)]
pub fn current_loglevel() -> u8 {
use std::{os::unix::ffi::OsStrExt, sync::LazyLock};
static LOGLEVEL: LazyLock<u8> = LazyLock::new(|| {
std::env::var_os(crate::config::ENV_LOG)
.map(|val| parse_loglevel(val.as_os_str().as_bytes(), LogLevel::Warn))
.unwrap_or(LogLevel::Warn)
.as_u8()
});
*LOGLEVEL
}
#[macro_export]
macro_rules! log_enabled {
($level:expr) => {
$crate::syslog::current_loglevel() >= $level.as_u8()
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_level_1() {
assert_eq!(LogLevel::Emergent.as_u8(), 0);
assert_eq!(LogLevel::Alert.as_u8(), 1);
assert_eq!(LogLevel::Crit.as_u8(), 2);
assert_eq!(LogLevel::Err.as_u8(), 3);
assert_eq!(LogLevel::Warn.as_u8(), 4);
assert_eq!(LogLevel::Notice.as_u8(), 5);
assert_eq!(LogLevel::Info.as_u8(), 6);
assert_eq!(LogLevel::Debug.as_u8(), 7);
}
#[test]
fn test_log_level_2() {
assert_eq!(LogLevel::Emergent.as_bytes(), b"emerg");
assert_eq!(LogLevel::Alert.as_bytes(), b"alert");
assert_eq!(LogLevel::Crit.as_bytes(), b"crit");
assert_eq!(LogLevel::Err.as_bytes(), b"error");
assert_eq!(LogLevel::Warn.as_bytes(), b"warn");
assert_eq!(LogLevel::Notice.as_bytes(), b"notice");
assert_eq!(LogLevel::Info.as_bytes(), b"info");
assert_eq!(LogLevel::Debug.as_bytes(), b"debug");
}
#[test]
fn test_log_level_3() {
assert_eq!(LogLevel::from(0u8), LogLevel::Emergent);
assert_eq!(LogLevel::from(1u8), LogLevel::Alert);
assert_eq!(LogLevel::from(2u8), LogLevel::Crit);
assert_eq!(LogLevel::from(3u8), LogLevel::Err);
assert_eq!(LogLevel::from(4u8), LogLevel::Warn);
assert_eq!(LogLevel::from(5u8), LogLevel::Notice);
assert_eq!(LogLevel::from(6u8), LogLevel::Info);
assert_eq!(LogLevel::from(7u8), LogLevel::Debug);
}
#[test]
fn test_log_level_4() {
assert_eq!(LogLevel::from(8u8), LogLevel::Debug);
assert_eq!(LogLevel::from(255u8), LogLevel::Debug);
}
#[test]
fn test_log_level_5() {
assert_eq!(LogLevel::from(0i64), LogLevel::Emergent);
assert_eq!(LogLevel::from(3i64), LogLevel::Err);
assert_eq!(LogLevel::from(7i64), LogLevel::Debug);
}
#[test]
fn test_log_level_6() {
assert_eq!(LogLevel::from(-1i64), LogLevel::Emergent);
assert_eq!(LogLevel::from(-100i64), LogLevel::Emergent);
}
#[test]
fn test_log_level_7() {
assert_eq!(LogLevel::from(100i64), LogLevel::Debug);
}
#[test]
fn test_log_level_8() {
assert!(LogLevel::Emergent < LogLevel::Alert);
assert!(LogLevel::Alert < LogLevel::Crit);
assert!(LogLevel::Crit < LogLevel::Err);
assert!(LogLevel::Err < LogLevel::Warn);
assert!(LogLevel::Warn < LogLevel::Notice);
assert!(LogLevel::Notice < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Debug);
}
#[test]
fn test_log_level_9() {
let level = LogLevel::Info;
let cloned = level;
assert_eq!(level, cloned);
}
#[test]
fn test_parse_loglevel_1() {
assert_eq!(parse_loglevel(b"0", LogLevel::Warn), LogLevel::Emergent);
assert_eq!(parse_loglevel(b"3", LogLevel::Warn), LogLevel::Err);
assert_eq!(parse_loglevel(b"7", LogLevel::Warn), LogLevel::Debug);
}
#[test]
fn test_parse_loglevel_2() {
assert_eq!(parse_loglevel(b"emerg", LogLevel::Warn), LogLevel::Emergent);
assert_eq!(parse_loglevel(b"alert", LogLevel::Warn), LogLevel::Alert);
assert_eq!(parse_loglevel(b"crit", LogLevel::Warn), LogLevel::Crit);
assert_eq!(parse_loglevel(b"error", LogLevel::Warn), LogLevel::Err);
assert_eq!(parse_loglevel(b"warn", LogLevel::Warn), LogLevel::Warn);
assert_eq!(parse_loglevel(b"notice", LogLevel::Warn), LogLevel::Notice);
assert_eq!(parse_loglevel(b"info", LogLevel::Warn), LogLevel::Info);
assert_eq!(parse_loglevel(b"debug", LogLevel::Warn), LogLevel::Debug);
}
#[test]
fn test_parse_loglevel_3() {
assert_eq!(parse_loglevel(b"", LogLevel::Info), LogLevel::Info);
}
#[test]
fn test_parse_loglevel_4() {
assert_eq!(parse_loglevel(b" ", LogLevel::Info), LogLevel::Info);
}
#[test]
fn test_parse_loglevel_5() {
assert_eq!(parse_loglevel(b" debug ", LogLevel::Warn), LogLevel::Debug);
}
#[test]
fn test_parse_loglevel_6() {
assert_eq!(parse_loglevel(b"unknown", LogLevel::Info), LogLevel::Info);
}
#[test]
fn test_parse_loglevel_7() {
assert_eq!(parse_loglevel(b"-1", LogLevel::Warn), LogLevel::Emergent);
}
#[test]
fn test_parse_loglevel_8() {
assert_eq!(parse_loglevel(b"100", LogLevel::Warn), LogLevel::Debug);
}
#[test]
fn test_syslog_1() {
assert_eq!(SYSLOG_ACTION_CLOSE, 0);
assert_eq!(SYSLOG_ACTION_OPEN, 1);
assert_eq!(SYSLOG_ACTION_READ, 2);
assert_eq!(SYSLOG_ACTION_READ_ALL, 3);
assert_eq!(SYSLOG_ACTION_READ_CLEAR, 4);
assert_eq!(SYSLOG_ACTION_CLEAR, 5);
assert_eq!(SYSLOG_ACTION_CONSOLE_OFF, 6);
assert_eq!(SYSLOG_ACTION_CONSOLE_ON, 7);
assert_eq!(SYSLOG_ACTION_CONSOLE_LEVEL, 8);
assert_eq!(SYSLOG_ACTION_SIZE_UNREAD, 9);
assert_eq!(SYSLOG_ACTION_SIZE_BUFFER, 10);
}
#[cfg(feature = "log")]
mod syslog_tests {
use nix::errno::Errno;
use super::*;
use crate::{
config::DEFAULT_LOG_RLIMIT_BURST,
log::{LogMap, LogValue},
};
#[test]
fn test_syslog_1() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
assert!(!syslog.is_locked());
assert_eq!(syslog.loglevel(), LogLevel::Info.as_u8());
}
#[test]
fn test_syslog_2() {
let syslog = Syslog::new(0, LogLevel::Warn, true);
assert!(!syslog.is_locked());
assert_eq!(syslog.loglevel(), LogLevel::Warn.as_u8());
}
#[test]
fn test_syslog_3() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
syslog.set_loglevel(0);
assert_eq!(syslog.loglevel(), 0);
syslog.set_loglevel(7);
assert_eq!(syslog.loglevel(), 7);
syslog.set_loglevel(255);
assert_eq!(syslog.loglevel(), 7);
}
#[test]
fn test_syslog_4() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
assert!(!syslog.is_locked());
assert!(syslog.lock());
assert!(syslog.is_locked());
assert!(!syslog.lock());
}
#[test]
fn test_syslog_5() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
syslog.lock();
let result = syslog.syslog(SYSLOG_ACTION_READ_ALL, 100);
assert_eq!(result, Err(Errno::EPERM));
}
#[test]
fn test_syslog_6() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
assert_eq!(syslog.syslog(SYSLOG_ACTION_CLOSE, 0), Ok((0, None)));
assert_eq!(syslog.syslog(SYSLOG_ACTION_OPEN, 0), Ok((0, None)));
}
#[test]
fn test_syslog_7() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
assert_eq!(syslog.syslog(99, 100), Err(Errno::EINVAL));
}
#[test]
fn test_syslog_8() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
assert_eq!(syslog.syslog(SYSLOG_ACTION_READ, 0), Ok((0, None)));
assert_eq!(syslog.syslog(SYSLOG_ACTION_READ_ALL, 0), Ok((0, None)));
assert_eq!(syslog.syslog(SYSLOG_ACTION_READ_CLEAR, 0), Ok((0, None)));
}
#[test]
fn test_syslog_9() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
syslog.syslog(SYSLOG_ACTION_CONSOLE_OFF, 0).unwrap();
assert_eq!(syslog.loglevel(), LogLevel::Emergent.as_u8());
syslog.syslog(SYSLOG_ACTION_CONSOLE_ON, 0).unwrap();
assert_eq!(syslog.loglevel(), LogLevel::Warn.as_u8());
}
#[test]
fn test_syslog_10() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
syslog.syslog(SYSLOG_ACTION_CONSOLE_LEVEL, 5).unwrap();
assert_eq!(syslog.loglevel(), 5);
}
#[test]
fn test_syslog_11() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
let (cap, _) = syslog.syslog(SYSLOG_ACTION_SIZE_BUFFER, 0).unwrap();
assert!(cap > 0);
}
#[test]
fn test_syslog_12() {
let syslog = Syslog::new(1024, LogLevel::Info, false);
let (unread, _) = syslog.syslog(SYSLOG_ACTION_SIZE_UNREAD, 0).unwrap();
assert_eq!(unread, 0);
}
#[test]
fn test_syslog_13() {
let syslog = Syslog::new(4096, LogLevel::Debug, false);
let mut map = LogMap::new();
let _ = map.try_insert("msg", LogValue::Borrowed("test message"));
syslog.write_log(LogLevel::Info, &map, false, 0);
let (unread, _) = syslog.syslog(SYSLOG_ACTION_SIZE_UNREAD, 0).unwrap();
assert!(unread > 0);
let (count, data) = syslog.syslog(SYSLOG_ACTION_READ_ALL, 4096).unwrap();
assert!(count > 0);
let data = data.unwrap();
let text = String::from_utf8_lossy(&data);
assert!(text.contains("test message"));
}
#[test]
fn test_syslog_14() {
let syslog = Syslog::new(4096, LogLevel::Err, false);
let mut map = LogMap::new();
let _ = map.try_insert("msg", LogValue::Borrowed("filtered message"));
syslog.write_log(LogLevel::Info, &map, false, 0);
let (unread, _) = syslog.syslog(SYSLOG_ACTION_SIZE_UNREAD, 0).unwrap();
assert_eq!(unread, 0);
}
#[test]
fn test_syslog_15() {
let syslog = Syslog::new(4096, LogLevel::Debug, false);
let mut map = LogMap::new();
let _ = map.try_insert("msg", LogValue::Borrowed("clear test"));
syslog.write_log(LogLevel::Info, &map, false, 0);
syslog.syslog(SYSLOG_ACTION_CLEAR, 0).unwrap();
let (count, _) = syslog.syslog(SYSLOG_ACTION_READ_ALL, 4096).unwrap();
assert_eq!(count, 0);
}
#[test]
fn test_syslog_16() {
let syslog = Syslog::new(4096, LogLevel::Debug, false);
let mut map = LogMap::new();
let _ = map.try_insert("msg", LogValue::Borrowed("read_clear test"));
syslog.write_log(LogLevel::Info, &map, false, 0);
let (count, data) = syslog.syslog(SYSLOG_ACTION_READ_CLEAR, 4096).unwrap();
assert!(count > 0);
assert!(data.is_some());
let (unread, _) = syslog.syslog(SYSLOG_ACTION_SIZE_UNREAD, 0).unwrap();
assert_eq!(unread, 0);
}
fn compute_msg_hash(map: &crate::log::LogMap) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::hash::DefaultHasher::new();
map.hash(&mut hasher);
hasher.finish()
}
#[test]
fn test_syslog_17() {
let mut a = LogMap::new();
let _ = a.try_insert("ctx", LogValue::Borrowed("test"));
let _ = a.try_insert("msg", LogValue::Borrowed("denied"));
let mut b = LogMap::new();
let _ = b.try_insert("ctx", LogValue::Borrowed("test"));
let _ = b.try_insert("msg", LogValue::Borrowed("denied"));
assert_eq!(compute_msg_hash(&a), compute_msg_hash(&b));
}
#[test]
fn test_syslog_18() {
let mut a = LogMap::new();
let _ = a.try_insert("ctx", LogValue::Borrowed("test"));
let _ = a.try_insert("msg", LogValue::Borrowed("denied"));
let mut b = LogMap::new();
let _ = b.try_insert("ctx", LogValue::Borrowed("test"));
let _ = b.try_insert("msg", LogValue::Borrowed("allowed"));
assert_ne!(compute_msg_hash(&a), compute_msg_hash(&b));
}
#[test]
fn test_syslog_19() {
let mut original = LogMap::new();
let _ = original.try_insert("ctx", LogValue::Borrowed("test"));
let hash_before = compute_msg_hash(&original);
let mut enriched = LogMap::new();
let _ = enriched.try_insert("ctx", LogValue::Borrowed("test"));
let _ = enriched.try_insert("time", LogValue::Borrowed("2026-01-01"));
let _ = enriched.try_insert("pid", LogValue::from(42i32));
assert_eq!(hash_before, compute_msg_hash(&enriched));
assert_eq!(hash_before, compute_msg_hash(&original));
}
#[test]
fn test_syslog_20() {
let syslog = Syslog::new(8192, LogLevel::Debug, false);
let mut map = LogMap::new();
let _ = map.try_insert("ctx", LogValue::Borrowed("spam"));
let hash = compute_msg_hash(&map);
for _ in 0..15 {
syslog.write_log(LogLevel::Info, &map, false, hash);
}
let (count, data) = syslog.syslog(SYSLOG_ACTION_READ_ALL, 8192).unwrap();
assert!(count > 0);
let data = data.unwrap();
let text = String::from_utf8_lossy(&data);
let lines: Vec<&str> = text.lines().filter(|l| l.contains("spam")).collect();
assert_eq!(lines.len(), DEFAULT_LOG_RLIMIT_BURST as usize);
}
#[test]
fn test_syslog_21() {
let syslog = Syslog::new(8192, LogLevel::Debug, false);
let mut map_a = LogMap::new();
let _ = map_a.try_insert("ctx", LogValue::Borrowed("aaa"));
let hash_a = compute_msg_hash(&map_a);
let mut map_b = LogMap::new();
let _ = map_b.try_insert("ctx", LogValue::Borrowed("bbb"));
let hash_b = compute_msg_hash(&map_b);
assert_ne!(hash_a, hash_b);
for _ in 0..5 {
syslog.write_log(LogLevel::Info, &map_a, false, hash_a);
syslog.write_log(LogLevel::Info, &map_b, false, hash_b);
}
let (_, data) = syslog.syslog(SYSLOG_ACTION_READ_ALL, 8192).unwrap();
let data = data.unwrap();
let text = String::from_utf8_lossy(&data);
let a_count = text.lines().filter(|l| l.contains("aaa")).count();
let b_count = text.lines().filter(|l| l.contains("bbb")).count();
assert_eq!(a_count, 5);
assert_eq!(b_count, 5);
}
}
}