From e48536dfbe49f09e54e9c4c3a6e7cb03513ec4d0 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Mon, 18 May 2026 17:13:23 -0700 Subject: [PATCH] =?UTF-8?q?feat(m1):=20journald=20watcher=20=E2=80=94=20st?= =?UTF-8?q?reams=20journalctl=20JSON=20to=20event=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/watcher/journald.rs | 71 +++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/watcher/journald.rs b/src-tauri/src/watcher/journald.rs index 01f1d00..c1c3564 100644 --- a/src-tauri/src/watcher/journald.rs +++ b/src-tauri/src/watcher/journald.rs @@ -1,6 +1,71 @@ -use super::SystemEvent; +use super::{EventSource, SystemEvent, now_unix}; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Command; use tokio::sync::mpsc; -pub async fn watch(_tx: mpsc::Sender) { - // implemented in Task 6 +pub async fn watch(tx: mpsc::Sender) { + let mut child = match Command::new("journalctl") + .args(["--follow", "--output=json", "--lines=0"]) + .stdout(std::process::Stdio::piped()) + .spawn() + { + Ok(c) => c, + Err(e) => { + log::error!("journald watcher: failed to spawn journalctl: {e}"); + return; + } + }; + + let stdout = match child.stdout.take() { + Some(s) => s, + None => return, + }; + + let mut lines = BufReader::new(stdout).lines(); + while let Ok(Some(line)) = lines.next_line().await { + if let Some(msg) = extract_message(&line) { + let _ = tx + .send(SystemEvent { + source: EventSource::Journald, + raw_line: msg, + timestamp: now_unix(), + }) + .await; + } + } +} + +fn extract_message(line: &str) -> Option { + let json: serde_json::Value = serde_json::from_str(line).ok()?; + let msg = json.get("MESSAGE")?.as_str()?; + if msg.is_empty() { + return None; + } + Some(msg.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn extract_message_from_journald_json() { + let line = r#"{"MESSAGE":"AUR build failed for foo","PRIORITY":"3","_COMM":"makepkg"}"#; + assert_eq!( + extract_message(line), + Some("AUR build failed for foo".to_string()) + ); + } + + #[test] + fn extract_message_missing_returns_none() { + let line = r#"{"PRIORITY":"6","_COMM":"systemd"}"#; + assert_eq!(extract_message(line), None); + } + + #[test] + fn extract_message_empty_skipped() { + let line = r#"{"MESSAGE":"","PRIORITY":"6"}"#; + assert_eq!(extract_message(line), None); + } }