diff --git a/proxmox-system-config-api/Cargo.toml b/proxmox-system-config-api/Cargo.toml index 81f1d32b..9ef4f8e5 100644 --- a/proxmox-system-config-api/Cargo.toml +++ b/proxmox-system-config-api/Cargo.toml @@ -18,6 +18,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } nix = { workspace = true, optional = true } libc = { workspace = true, optional = true } +log = { workspace = true, optional = true } proxmox-sys = { workspace = true, optional = true } proxmox-schema = { workspace = true, features = ["api-macro", "api-types"] } @@ -51,3 +52,8 @@ network-impl = [ "dep:libc", "dep:proxmox-sys", ] +syslog = [] +syslog-impl = [ + "syslog", + "dep:log", +] diff --git a/proxmox-system-config-api/src/lib.rs b/proxmox-system-config-api/src/lib.rs index b69655a8..67d69516 100644 --- a/proxmox-system-config-api/src/lib.rs +++ b/proxmox-system-config-api/src/lib.rs @@ -6,3 +6,6 @@ pub mod network; #[cfg(feature = "time")] pub mod time; + +#[cfg(feature = "syslog")] +pub mod syslog; \ No newline at end of file diff --git a/proxmox-system-config-api/src/syslog/api_types.rs b/proxmox-system-config-api/src/syslog/api_types.rs new file mode 100644 index 00000000..8bf99cac --- /dev/null +++ b/proxmox-system-config-api/src/syslog/api_types.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; + +use proxmox_schema::api; +use proxmox_schema::api_types::SYSTEMD_DATETIME_FORMAT; + +#[api( + properties: { + start: { + type: Integer, + description: "Start line number.", + minimum: 0, + optional: true, + }, + limit: { + type: Integer, + description: "Max. number of lines.", + optional: true, + minimum: 0, + }, + since: { + type: String, + optional: true, + description: "Display all log since this date-time string.", + format: &SYSTEMD_DATETIME_FORMAT, + }, + until: { + type: String, + optional: true, + description: "Display all log until this date-time string.", + format: &SYSTEMD_DATETIME_FORMAT, + }, + service: { + type: String, + optional: true, + description: "Service ID.", + max_length: 128, + }, + }, +)] +#[derive(Clone, PartialEq, Serialize, Deserialize)] +/// Syslog filtering options. +pub struct SyslogFilter { + pub start: Option, + pub limit: Option, + pub since: Option, + pub until: Option, + pub service: Option, +} + +#[api] +#[derive(Clone, PartialEq, Serialize, Deserialize)] +/// Syslog line with line number. +pub struct SyslogLine { + /// Line number. + pub n: u64, + /// Line text. + pub t: String, +} \ No newline at end of file diff --git a/proxmox-system-config-api/src/syslog/journal.rs b/proxmox-system-config-api/src/syslog/journal.rs new file mode 100644 index 00000000..73b5c96d --- /dev/null +++ b/proxmox-system-config-api/src/syslog/journal.rs @@ -0,0 +1,73 @@ +use std::process::{Command, Stdio}; + +use anyhow::Error; + +use super::{SyslogFilter, SyslogLine}; + +pub fn dump_journal(filter: SyslogFilter) -> Result<(u64, Vec), Error> { + let mut args = vec!["-o", "short", "--no-pager"]; + + if let Some(service) = &filter.service { + args.extend(["--unit", service]); + } + if let Some(since) = &filter.since { + args.extend(["--since", since]); + } + if let Some(until) = &filter.until { + args.extend(["--until", until]); + } + + let mut lines: Vec = Vec::new(); + let mut limit = filter.limit.unwrap_or(50); + let start = filter.start.unwrap_or(0); + let mut count: u64 = 0; + + let mut child = Command::new("journalctl") + .args(&args) + .stdout(Stdio::piped()) + .spawn()?; + + use std::io::{BufRead, BufReader}; + + if let Some(ref mut stdout) = child.stdout { + for line in BufReader::new(stdout).lines() { + match line { + Ok(line) => { + count += 1; + if count < start { + continue; + }; + if limit == 0 { + continue; + }; + + lines.push(SyslogLine { n: count, t: line }); + + limit -= 1; + } + Err(err) => { + log::error!("reading journal failed: {}", err); + let _ = child.kill(); + break; + } + } + } + } + + let status = child.wait().unwrap(); + if !status.success() { + log::error!("journalctl failed with {}", status); + } + + // HACK: ExtJS store.guaranteeRange() does not like empty array + // so we add a line + if count == 0 { + count += 1; + lines.push(SyslogLine { + n: count, + t: String::from("no content"), + }); + } + + Ok((count, lines)) +} diff --git a/proxmox-system-config-api/src/syslog/mod.rs b/proxmox-system-config-api/src/syslog/mod.rs new file mode 100644 index 00000000..bcace64b --- /dev/null +++ b/proxmox-system-config-api/src/syslog/mod.rs @@ -0,0 +1,8 @@ +mod api_types; +pub use api_types::*; + + +#[cfg(feature = "syslog-impl")] +mod journal; +#[cfg(feature = "syslog-impl")] +pub use journal::dump_journal;