From b42b3a4823f585067b3b669efade2c761d7ea275 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 9 Oct 2024 11:15:07 +0200 Subject: [PATCH] New log watcher (#324) * New log watcher * fix inifnite loop, don't parse empty lines * change logs --------- Co-authored-by: Aleksander <170264518+t-aleksander@users.noreply.github.com> --- src-tauri/Cargo.lock | 183 ++++++--------------------- src-tauri/Cargo.toml | 1 - src-tauri/src/service/log_watcher.rs | 124 ++++++++---------- 3 files changed, 96 insertions(+), 212 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2994f926..ccd3362f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -759,9 +759,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.24" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -1269,7 +1269,6 @@ dependencies = [ "local-ip-address", "log", "nix 0.29.0", - "notify-debouncer-mini", "prost", "prost-build", "rand 0.8.5", @@ -1839,15 +1838,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "funty" version = "2.0.0" @@ -1866,9 +1856,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1876,15 +1866,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1904,9 +1894,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1938,9 +1928,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1949,21 +1939,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -2128,9 +2118,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" @@ -2775,26 +2765,6 @@ dependencies = [ "cfb", ] -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - [[package]] name = "instant" version = "0.1.13" @@ -2817,9 +2787,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" @@ -2954,26 +2924,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "kqueue" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - [[package]] name = "kuchikiki" version = "0.8.2" @@ -3262,18 +3212,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.0.2" @@ -3496,36 +3434,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "notify" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" -dependencies = [ - "bitflags 2.6.0", - "crossbeam-channel", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio 0.8.11", - "walkdir", - "windows-sys 0.48.0", -] - -[[package]] -name = "notify-debouncer-mini" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43" -dependencies = [ - "crossbeam-channel", - "log", - "notify", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3759,21 +3667,18 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" @@ -4102,18 +4007,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", @@ -4227,12 +4132,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -4842,9 +4741,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "rustls-pki-types", @@ -4911,9 +4810,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -5055,9 +4954,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", @@ -5073,9 +4972,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", @@ -6173,7 +6072,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1892ce6e..cf08d3a4 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,7 +27,6 @@ dirs = "5.0" lazy_static = "1.5" local-ip-address = "0.6" log = "0.4" -notify-debouncer-mini = "0.4" prost = "0.13" rand = "0.8" reqwest = { version = "0.12", features = ["json"] } diff --git a/src-tauri/src/service/log_watcher.rs b/src-tauri/src/service/log_watcher.rs index a7c6504e..e204e7c5 100644 --- a/src-tauri/src/service/log_watcher.rs +++ b/src-tauri/src/service/log_watcher.rs @@ -9,14 +9,11 @@ use std::{ io::{BufRead, BufReader}, path::{Path, PathBuf}, str::FromStr, - time::{Duration, SystemTime}, + thread::sleep, + time::Duration, }; -use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; -use notify_debouncer_mini::{ - new_debouncer, - notify::{self, RecursiveMode}, -}; +use chrono::{DateTime, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use tauri::{async_runtime::TokioJoinHandle, AppHandle, Manager}; @@ -29,15 +26,15 @@ use crate::{ ConnectionType, }; -#[derive(Error, Debug)] +const DELAY: Duration = Duration::from_secs(2); + +#[derive(Debug, Error)] pub enum LogWatcherError { #[error(transparent)] TauriError(#[from] tauri::Error), #[error(transparent)] SerdeJsonError(#[from] serde_json::Error), #[error(transparent)] - NotifyError(#[from] notify::Error), - #[error(transparent)] IoError(#[from] std::io::Error), } @@ -72,7 +69,6 @@ pub struct ServiceLogWatcher<'a> { from: Option>, log_dir: &'a Path, current_log_file: Option, - current_position: u64, handle: AppHandle, cancellation_token: CancellationToken, event_topic: String, @@ -97,7 +93,6 @@ impl<'a> ServiceLogWatcher<'a> { from, log_dir, current_log_file: None, - current_position: 0, handle, cancellation_token, event_topic, @@ -108,32 +103,19 @@ impl<'a> ServiceLogWatcher<'a> { /// /// Setup a directory watcher with a 2 second debounce and parse the log dir on each change. pub fn run(&mut self) -> Result<(), LogWatcherError> { - // setup debouncer - let (tx, rx) = std::sync::mpsc::channel(); - let mut debouncer = new_debouncer(Duration::from_secs(2), tx)?; - - debouncer - .watcher() - .watch(self.log_dir, RecursiveMode::Recursive)?; - - // parse log dir initially before watching for changes - self.parse_log_dir()?; + // get latest log file + let latest_log_file = self.get_latest_log_file()?; + debug!("found latest log file: {latest_log_file:?}"); + self.current_log_file = latest_log_file; - for result in rx { + // indefinitely watch for changes + loop { + self.parse_log_dir()?; if self.cancellation_token.is_cancelled() { - info!( - "Received cancellation request. Stopping log watcher for interface {}", - self.interface_name - ); break; - } - match result { - Ok(_events) => { - self.parse_log_dir()?; - } - Err(error) => println!("Error {error:?}"), - } + }; } + Ok(()) } @@ -145,38 +127,48 @@ impl<'a> ServiceLogWatcher<'a> { /// so only new log lines are sent to the frontend whenever a change in /// the directory is detected. fn parse_log_dir(&mut self) -> Result<(), LogWatcherError> { - // get latest log file - let latest_log_file = self.get_latest_log_file()?; - debug!("found latest log file: {latest_log_file:?}"); - - // check if latest file changed - if latest_log_file.is_some() && latest_log_file != self.current_log_file { - self.current_log_file = latest_log_file; - // reset read position - self.current_position = 0; - } - // read and parse file from last position if let Some(log_file) = &self.current_log_file { let file = File::open(log_file)?; - let size = file.metadata()?.len(); let mut reader = BufReader::new(file); - reader.seek_relative(self.current_position as i64)?; + let mut line = String::new(); let mut parsed_lines = Vec::new(); - for line in reader.lines() { - let line = line?; - if let Some(parsed_line) = self.parse_log_line(&line)? { - parsed_lines.push(parsed_line); + loop { + let size = reader.read_line(&mut line)?; + if size == 0 { + // emit event with all relevant log lines + if !parsed_lines.is_empty() { + trace!("Emitting {} log lines for the frontend", parsed_lines.len()); + self.handle.emit_all(&self.event_topic, &parsed_lines)?; + } + parsed_lines.clear(); + + sleep(DELAY); + + let latest_log_file = self.get_latest_log_file()?; + if latest_log_file.is_some() && latest_log_file != self.current_log_file { + debug!( + "New log file detected. Switching to new log file: {latest_log_file:?}" + ); + self.current_log_file = latest_log_file; + break; + } + } else { + if let Some(parsed_line) = self.parse_log_line(&line)? { + parsed_lines.push(parsed_line); + } + line.clear(); + } + if self.cancellation_token.is_cancelled() { + info!( + "Received cancellation request. Stopping log watcher for interface {}", + self.interface_name + ); + break; } } - // emit event with all relevant log lines - if !parsed_lines.is_empty() { - self.handle.emit_all(&self.event_topic, parsed_lines)?; - } - - // update read position to end of file - self.current_position = size; } + Ok(()) } @@ -224,11 +216,11 @@ impl<'a> ServiceLogWatcher<'a> { /// Log files are rotated daily and have a knows naming format, /// with the last 10 characters specifying a date (e.g. `2023-12-15`). fn get_latest_log_file(&self) -> Result, LogWatcherError> { - debug!("Getting latest log file from directory: {:?}", self.log_dir); + trace!("Getting latest log file from directory: {:?}", self.log_dir); let entries = read_dir(self.log_dir)?; let mut latest_log = None; - let mut latest_time = SystemTime::UNIX_EPOCH; + let mut latest_time = NaiveDate::MIN; for entry in entries.flatten() { // skip directories if entry.metadata()?.is_file() { @@ -241,24 +233,18 @@ impl<'a> ServiceLogWatcher<'a> { } } } + Ok(latest_log) } } -fn extract_timestamp(filename: &str) -> Option { +fn extract_timestamp(filename: &str) -> Option { trace!("Extracting timestamp from log file name: {filename}"); // we know that the date is always in the last 10 characters let split_pos = filename.char_indices().nth_back(9)?.0; let timestamp = &filename[split_pos..]; - // parse and convert to `SystemTime` - if let Ok(timestamp) = NaiveDate::parse_from_str(timestamp, "%Y-%m-%d") { - let timestamp = timestamp - .and_time(NaiveTime::default()) - .and_utc() - .timestamp(); - return Some(SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp as u64)); - } - None + // parse and convert to `NaiveDate` + NaiveDate::parse_from_str(timestamp, "%Y-%m-%d").ok() } /// Starts a log watcher in a separate thread