use crate::events::{Event, EventBus};
pub struct SystemLogSink;
impl SystemLogSink {
pub fn spawn(bus: &EventBus) {
let mut rx = bus.subscribe();
let _ = std::thread::Builder::new()
.name("vetto-syslog".into())
.spawn(move || {
while let Ok(event) = rx.blocking_recv() {
let line = format_event_for_system_log(&event);
log_to_system(&line);
}
});
}
}
pub fn format_event_for_system_log(event: &Event) -> String {
match event {
Event::SessionStarted {
ts,
pid,
tier,
net_mode,
profile,
} => {
format!("VETTO_SESSION_START ts={ts} pid={pid} tier={tier} net={net_mode} profile={profile}")
}
Event::SessionEnded {
ts,
exit_code,
duration_secs,
} => {
format!("VETTO_SESSION_END ts={ts} exit_code={exit_code} duration_s={duration_secs}")
}
Event::BlockedAttempt {
ts,
pid,
comm,
path,
source,
} => {
format!(
"VETTO_BLOCKED_ATTEMPT ts={ts} pid={pid} comm={comm} path={path} source={source}"
)
}
Event::SecretMasked { ts, path } => {
format!("VETTO_SECRET_MASKED ts={ts} path={path}")
}
Event::Notice { ts, message } => {
format!("VETTO_NOTICE ts={ts} msg={message}")
}
Event::SessionTimeout { ts } => {
format!("VETTO_SESSION_TIMEOUT ts={ts}")
}
_ => format!("VETTO_EVENT ts={} kind={}", event.ts(), event.kind()),
}
}
pub fn log_to_system(message: &str) {
#[cfg(target_os = "linux")]
{
log_linux(message);
}
#[cfg(target_os = "macos")]
{
log_macos(message);
}
#[cfg(target_os = "windows")]
{
log_windows(message);
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
let _ = message;
}
}
#[cfg(target_os = "linux")]
fn log_linux(message: &str) {
use std::os::unix::net::UnixDatagram;
let socket_path = "/run/systemd/journal/socket";
if std::path::Path::new(socket_path).exists() {
if let Ok(sock) = UnixDatagram::unbound() {
let payload = format!("MESSAGE={message}\nPRIORITY=6\nSYSLOG_IDENTIFIER=vetto\n\n");
if sock.send_to(payload.as_bytes(), socket_path).is_ok() {
return;
}
}
}
let _ = std::process::Command::new("logger")
.args(["-t", "vetto", message])
.output();
}
#[cfg(target_os = "macos")]
fn log_macos(message: &str) {
let _ = std::process::Command::new("logger")
.args(["-t", "vetto", "-s", message])
.output();
}
#[cfg(target_os = "windows")]
fn log_windows(message: &str) {
let _ = std::process::Command::new("eventcreate")
.args([
"/L",
"APPLICATION",
"/T",
"INFORMATION",
"/ID",
"1",
"/SO",
"Vetto",
"/D",
message,
])
.output();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_events_correctly() {
let ev = Event::Notice {
ts: crate::events::types::now(),
message: "test notice".into(),
};
let line = format_event_for_system_log(&ev);
assert!(line.contains("VETTO_NOTICE"));
assert!(line.contains("test notice"));
}
#[test]
fn log_to_system_does_not_panic() {
log_to_system("test message safe");
}
}