From 71267870b0cd6158c828ede27c2b5ef4079c2304 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 19 Jan 2026 17:31:12 -0600 Subject: [PATCH] refactor: remove log dependency, replace with custom macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added custom logging macros (log_error!, log_warn!, log_info!, log_debug!) - Implemented LogLevel enum and LOG_LEVEL static for runtime level control - Exported macros with #[macro_export] for use across all modules - Replaced all log:: calls in websocket_client.rs, health.rs, and version.rs - Removed obsolete test code referencing old log crate - Removed log dependency from Cargo.toml - Fixed useless comparison warning in test - Binary size: still 1.5M (no change - log was lightweight) - Tests: 65 passing, 0 warnings This completes the removal of 9 dependencies (thiserror, hostname, chrono, base64, rand, ureq, tiny_http, anyhow, log). Dependency count: 19 → 10. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- Cargo.lock | 1 - Cargo.toml | 1 - src/health.rs | 5 ++-- src/main.rs | 62 ++++++++++------------------------------- src/version.rs | 6 ++-- src/websocket_client.rs | 54 +++++++++++++++++------------------ 6 files changed, 45 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adbfa0c..41b22b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1085,7 +1085,6 @@ version = "0.1.0" dependencies = [ "clap", "futures", - "log", "prost", "prost-build", "prost-types", diff --git a/Cargo.toml b/Cargo.toml index 947ddf3..e086704 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] } futures = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -log = { version = "0.4", features = ["std"] } clap = { version = "4.0", features = ["derive", "env"] } prost = "0.13" prost-types = "0.13" diff --git a/src/health.rs b/src/health.rs index a85858b..fdddb45 100644 --- a/src/health.rs +++ b/src/health.rs @@ -1,4 +1,3 @@ -use log::info; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::time::Instant; @@ -19,7 +18,7 @@ pub async fn start_health_server(port: u16) -> Result<()> { let start_time = Arc::new(Instant::now()); let addr = format!("0.0.0.0:{}", port); - info!("Starting health endpoint on {}", addr); + crate::log_info!("Starting health endpoint on {}", addr); let listener = TcpListener::bind(&addr).await?; loop { @@ -68,7 +67,7 @@ pub async fn start_health_server(port: u16) -> Result<()> { }); } Err(e) => { - log::warn!("Failed to accept health check connection: {}", e); + crate::log_warn!("Failed to accept health check connection: {}", e); } } } diff --git a/src/main.rs b/src/main.rs index af7fe21..36d0b1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,10 @@ use tokio::time::sleep; use websocket_client::AgentClient; // Log levels -static LOG_LEVEL: std::sync::OnceLock = std::sync::OnceLock::new(); +pub(crate) static LOG_LEVEL: std::sync::OnceLock = std::sync::OnceLock::new(); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum LogLevel { +pub(crate) enum LogLevel { Error = 1, Warn = 2, Info = 3, @@ -34,40 +34,44 @@ impl LogLevel { } } +#[macro_export] macro_rules! log_error { ($($arg:tt)*) => {{ - let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info); - if level >= LogLevel::Error { + let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info); + if level >= $crate::LogLevel::Error { let ts = $crate::format_timestamp(); eprintln!("[{}] [ERROR] {}", ts, format!($($arg)*)); } }}; } +#[macro_export] macro_rules! log_warn { ($($arg:tt)*) => {{ - let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info); - if level >= LogLevel::Warn { + let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info); + if level >= $crate::LogLevel::Warn { let ts = $crate::format_timestamp(); eprintln!("[{}] [WARN] {}", ts, format!($($arg)*)); } }}; } +#[macro_export] macro_rules! log_info { ($($arg:tt)*) => {{ - let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info); - if level >= LogLevel::Info { + let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info); + if level >= $crate::LogLevel::Info { let ts = $crate::format_timestamp(); eprintln!("[{}] [INFO] {}", ts, format!($($arg)*)); } }}; } +#[macro_export] macro_rules! log_debug { ($($arg:tt)*) => {{ - let level = LOG_LEVEL.get().copied().unwrap_or(LogLevel::Info); - if level >= LogLevel::Debug { + let level = $crate::LOG_LEVEL.get().copied().unwrap_or($crate::LogLevel::Info); + if level >= $crate::LogLevel::Debug { let ts = $crate::format_timestamp(); eprintln!("[{}] [DEBUG] {}", ts, format!($($arg)*)); } @@ -243,44 +247,6 @@ async fn main() { #[cfg(test)] mod tests { use super::*; - use log::Log; - - #[test] - fn test_simple_logger_enabled() { - let logger = SimpleLogger { - level: LevelFilter::Info, - }; - - // Info level should be enabled - let info_metadata = log::MetadataBuilder::new() - .level(log::Level::Info) - .target("test") - .build(); - assert!(logger.enabled(&info_metadata)); - - // Debug level should not be enabled - let debug_metadata = log::MetadataBuilder::new() - .level(log::Level::Debug) - .target("test") - .build(); - assert!(!logger.enabled(&debug_metadata)); - - // Error level should be enabled - let error_metadata = log::MetadataBuilder::new() - .level(log::Level::Error) - .target("test") - .build(); - assert!(logger.enabled(&error_metadata)); - } - - #[test] - fn test_simple_logger_flush() { - let logger = SimpleLogger { - level: LevelFilter::Info, - }; - // flush() does nothing, just verify it's callable - logger.flush(); - } #[test] fn test_convert_http_to_websocket() { diff --git a/src/version.rs b/src/version.rs index 8db5d18..b672db4 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,5 +1,3 @@ -use log::info; - // Get version at runtime - prefers BUILD_VERSION from build.rs, falls back to Cargo.toml fn current_version() -> &'static str { option_env!("BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")) @@ -8,8 +6,8 @@ fn current_version() -> &'static str { /// Startup check - logs current version pub fn check_for_updates() { let current_ver = current_version(); - info!("Current version: {}", current_ver); - info!("Watchtower will automatically update to new versions"); + crate::log_info!("Current version: {}", current_ver); + crate::log_info!("Watchtower will automatically update to new versions"); } #[cfg(test)] diff --git a/src/websocket_client.rs b/src/websocket_client.rs index 0ad6257..db8271b 100644 --- a/src/websocket_client.rs +++ b/src/websocket_client.rs @@ -59,17 +59,17 @@ impl AgentClient { // Strip trailing slash from base URL to avoid double slashes let base_url = url.trim_end_matches('/'); let ws_url = format!("{}/socket/agent/websocket", base_url); - log::info!("Connecting to WebSocket: {}", ws_url); + crate::log_info!("Connecting to WebSocket: {}", ws_url); let (mut ws_stream, _) = connect_async(&ws_url) .await .map_err(|e| { - log::error!("WebSocket connection failed: {}", e); + crate::log_error!("WebSocket connection failed: {}", e); e }) .map_err(|e| format!("Failed to connect to WebSocket: {}", e))?; - log::info!("Connected to Towerops server at {}", url); + crate::log_info!("Connected to Towerops server at {}", url); let agent_id = generate_agent_id(); let (result_tx, result_rx) = mpsc::unbounded_channel(); @@ -85,7 +85,7 @@ impl AgentClient { let join_text = serde_json::to_string(&join_msg)?; ws_stream.send(WsMessage::Text(join_text)).await?; - log::info!( + crate::log_info!( "Sent channel join request with token for agent:{}", agent_id ); @@ -122,15 +122,15 @@ impl AgentClient { self.handle_text_message(&text).await?; } Some(Ok(WsMessage::Close(_))) => { - log::info!("Server closed connection"); + crate::log_info!("Server closed connection"); break; } Some(Err(e)) => { - log::error!("WebSocket error: {}", e); + crate::log_error!("WebSocket error: {}", e); return Err(e.into()); } None => { - log::info!("Connection closed"); + crate::log_info!("Connection closed"); break; } _ => {} @@ -163,7 +163,7 @@ impl AgentClient { match phoenix_msg.event.as_str() { "phx_reply" => { - log::info!("Channel join reply: {:?}", phoenix_msg.payload); + crate::log_info!("Channel join reply: {:?}", phoenix_msg.payload); } "jobs" => { // Extract binary protobuf from payload @@ -176,7 +176,7 @@ impl AgentClient { } } _ => { - log::debug!("Ignoring unknown event: {}", phoenix_msg.event); + crate::log_debug!("Ignoring unknown event: {}", phoenix_msg.event); } } @@ -195,11 +195,11 @@ impl AgentClient { /// Process job list from server. async fn handle_jobs(&self, job_list: AgentJobList) -> Result<()> { - log::info!("Received {} jobs from server", job_list.jobs.len()); + crate::log_info!("Received {} jobs from server", job_list.jobs.len()); for job in job_list.jobs { let job_type = JobType::try_from(job.job_type).unwrap_or(JobType::Poll); - log::info!("Executing job: {} (type: {:?})", job.job_id, job_type); + crate::log_info!("Executing job: {} (type: {:?})", job.job_id, job_type); // Extract monitoring configuration before moving job let monitoring_info = job.snmp_device.as_ref().and_then(|snmp_device| { @@ -224,13 +224,13 @@ impl AgentClient { tokio::spawn(async move { if let Err(e) = execute_job(job, result_tx).await { - log::error!("Job execution failed: {}", e); + crate::log_error!("Job execution failed: {}", e); } }); // Start monitoring task if enabled if let Some((ip, interval_seconds)) = monitoring_info { - log::info!( + crate::log_info!( "Starting ICMP monitoring for device {} every {} seconds", device_id, interval_seconds @@ -246,7 +246,7 @@ impl AgentClient { ) .await { - log::error!("Monitoring task failed: {}", e); + crate::log_error!("Monitoring task failed: {}", e); } }); } @@ -277,7 +277,7 @@ impl AgentClient { let text = serde_json::to_string(&msg)?; self.ws_stream.send(WsMessage::Text(text)).await?; - log::debug!("Sent heartbeat"); + crate::log_debug!("Sent heartbeat"); Ok(()) } @@ -295,7 +295,7 @@ impl AgentClient { let text = serde_json::to_string(&msg)?; self.ws_stream.send(WsMessage::Text(text)).await?; - log::debug!("Sent SNMP result for device {}", result.device_id); + crate::log_debug!("Sent SNMP result for device {}", result.device_id); Ok(()) } @@ -313,7 +313,7 @@ impl AgentClient { let text = serde_json::to_string(&msg)?; self.ws_stream.send(WsMessage::Text(text)).await?; - log::debug!( + crate::log_debug!( "Sent monitoring check for device {}: {}", check.device_id, check.status @@ -351,7 +351,7 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender oid_values.insert(oid.clone(), value_to_string(value)); } Err(e) => { - log::warn!("SNMP GET failed for OID {}: {}", oid, e); + crate::log_warn!("SNMP GET failed for OID {}: {}", oid, e); } } } @@ -375,7 +375,7 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender } } Err(e) => { - log::warn!("SNMP WALK failed for OID {}: {}", base_oid, e); + crate::log_warn!("SNMP WALK failed for OID {}: {}", base_oid, e); } } } @@ -393,7 +393,7 @@ async fn execute_job(job: AgentJob, result_tx: mpsc::UnboundedSender .as_secs() as i64, }; - log::info!( + crate::log_info!( "Collected {} OID values for job {}", result.oid_values.len(), job.job_id @@ -549,7 +549,7 @@ async fn run_monitoring_task( let ip_addr: std::net::IpAddr = match ip.parse() { Ok(addr) => addr, Err(e) => { - log::error!("Invalid IP address for device {}: {}", device_id, e); + crate::log_error!("Invalid IP address for device {}: {}", device_id, e); continue; } }; @@ -568,11 +568,11 @@ async fn run_monitoring_task( }; if let Err(e) = check_tx.send(check) { - log::error!("Failed to send monitoring check: {}", e); + crate::log_error!("Failed to send monitoring check: {}", e); break; } - log::debug!( + crate::log_debug!( "ICMP ping successful for {}: {:.2}ms", ip, rtt.as_secs_f64() * 1000.0 @@ -589,11 +589,11 @@ async fn run_monitoring_task( }; if let Err(send_err) = check_tx.send(check) { - log::error!("Failed to send monitoring check: {}", send_err); + crate::log_error!("Failed to send monitoring check: {}", send_err); break; } - log::warn!("ICMP ping failed for {}: {}", ip, e); + crate::log_warn!("ICMP ping failed for {}: {}", ip, e); } } } @@ -663,8 +663,8 @@ mod tests { let uptime = get_uptime_seconds(); // On Linux with /proc/uptime, should return non-zero // On other platforms or if file doesn't exist, returns 0 - // Just verify it's callable and returns a number - assert!(uptime >= 0); + // uptime is u64, so always >= 0 - just verify it's callable + let _ = uptime; } #[test]