//! Syslog integration for btest-rs server mode. //! //! Sends structured log events to a remote syslog server via UDP (RFC 5424). //! Events: auth success/failure, test start/stop, speed results. use std::net::UdpSocket; use std::sync::Mutex; static SYSLOG: Mutex> = Mutex::new(None); struct SyslogSender { socket: UdpSocket, target: String, hostname: String, } /// Initialize the global syslog sender. /// `target` is the syslog server address, e.g. "192.168.1.1:514". pub fn init(target: &str) -> std::io::Result<()> { let socket = UdpSocket::bind("0.0.0.0:0")?; let hostname = hostname::get() .map(|h| h.to_string_lossy().to_string()) .unwrap_or_else(|_| "btest-rs".to_string()); let sender = SyslogSender { socket, target: target.to_string(), hostname, }; *SYSLOG.lock().unwrap() = Some(sender); tracing::info!("Syslog enabled, sending to {}", target); Ok(()) } /// Send a syslog message with the given severity and message. /// Severity: 6=info, 4=warning, 3=error fn send(severity: u8, msg: &str) { // Format timestamp outside the lock to minimize contention let priority = 128 + severity; let timestamp = bsd_timestamp(); let guard = SYSLOG.lock().unwrap(); if let Some(ref sender) = *guard { let syslog_msg = format!( "<{}>{} {} btest-rs: {}", priority, timestamp, sender.hostname, msg, ); let _ = sender.socket.send_to(syslog_msg.as_bytes(), &sender.target); } } fn bsd_timestamp() -> String { // RFC 3164 format: "Mon DD HH:MM:SS" (no year) chrono::Local::now().format("%b %e %H:%M:%S").to_string() } // --- Public logging functions --- pub fn auth_success(peer: &str, username: &str, auth_type: &str) { let msg = format!( "AUTH_SUCCESS peer={} user={} type={}", peer, username, auth_type, ); tracing::info!("{}", msg); send(6, &msg); } pub fn auth_failure(peer: &str, username: &str, auth_type: &str, reason: &str) { let msg = format!( "AUTH_FAILURE peer={} user={} type={} reason={}", peer, username, auth_type, reason, ); tracing::warn!("{}", msg); send(4, &msg); } pub fn test_start(peer: &str, proto: &str, direction: &str, conn_count: u8) { let msg = format!( "TEST_START peer={} proto={} dir={} connections={}", peer, proto, direction, conn_count.max(1), ); tracing::info!("{}", msg); send(6, &msg); } pub fn test_end( peer: &str, proto: &str, direction: &str, total_tx: u64, total_rx: u64, total_lost: u64, duration_secs: u32, ) { let tx_mbps = if duration_secs > 0 { total_tx as f64 * 8.0 / duration_secs as f64 / 1_000_000.0 } else { 0.0 }; let rx_mbps = if duration_secs > 0 { total_rx as f64 * 8.0 / duration_secs as f64 / 1_000_000.0 } else { 0.0 }; let msg = format!( "TEST_END peer={} proto={} dir={} duration={}s tx_avg={:.2}Mbps rx_avg={:.2}Mbps tx_bytes={} rx_bytes={} lost={}", peer, proto, direction, duration_secs, tx_mbps, rx_mbps, total_tx, total_rx, total_lost, ); tracing::info!("{}", msg); send(6, &msg); } /// Check if syslog is enabled. pub fn is_enabled() -> bool { SYSLOG.lock().unwrap().is_some() }