#[cfg(test)]
#[path = "syslog_tests.rs"]
mod syslog_tests;
use crate::traits::{LoaderInfo, LoaderType, LogLoader, Result, ScoutyError};
use std::net::UdpSocket;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct SyslogConfig {
pub bind_addr: String,
pub timeout: Duration,
pub max_messages: usize,
}
impl Default for SyslogConfig {
fn default() -> Self {
Self {
bind_addr: "127.0.0.1:1514".to_string(),
timeout: Duration::from_secs(5),
max_messages: 10000,
}
}
}
#[derive(Debug)]
pub struct SyslogLoader {
config: SyslogConfig,
info: LoaderInfo,
socket: Option<UdpSocket>,
}
impl SyslogLoader {
pub fn new(config: SyslogConfig) -> Self {
let id = format!("syslog:{}", config.bind_addr);
Self {
info: LoaderInfo {
id,
loader_type: LoaderType::Syslog,
multiline_enabled: false,
sample_lines: Vec::new(),
file_mod_year: None,
},
config,
socket: None,
}
}
fn ensure_socket(&mut self) -> Result<&UdpSocket> {
if self.socket.is_none() {
let sock = UdpSocket::bind(&self.config.bind_addr).map_err(|e| {
ScoutyError::Io(std::io::Error::new(
e.kind(),
format!(
"Failed to bind syslog socket to {}: {}",
self.config.bind_addr, e
),
))
})?;
sock.set_nonblocking(true).map_err(ScoutyError::Io)?;
self.socket = Some(sock);
}
Ok(self.socket.as_ref().unwrap())
}
}
impl LogLoader for SyslogLoader {
fn info(&self) -> &LoaderInfo {
&self.info
}
fn load(&mut self) -> Result<Vec<String>> {
self.ensure_socket()?;
let socket = self.socket.as_ref().unwrap();
let timeout = self.config.timeout;
let max_messages = self.config.max_messages;
let mut messages = Vec::new();
let mut buf = [0u8; 8192];
let start = Instant::now();
loop {
if start.elapsed() >= timeout {
break;
}
if max_messages > 0 && messages.len() >= max_messages {
break;
}
match socket.recv_from(&mut buf) {
Ok((len, _addr)) => {
if let Ok(msg) = std::str::from_utf8(&buf[..len]) {
let trimmed = msg.trim_end_matches(['\n', '\r']);
if !trimmed.is_empty() {
messages.push(trimmed.to_string());
}
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
if messages.is_empty() {
std::thread::sleep(Duration::from_millis(10));
} else {
break;
}
}
Err(e) => return Err(ScoutyError::Io(e)),
}
}
self.info.sample_lines = messages.iter().take(10).cloned().collect();
Ok(messages)
}
}