system-config-api: add syslog feature

Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
Dietmar Maurer 2024-05-15 12:31:50 +02:00
parent 87aaa4e30a
commit 15e3779331
5 changed files with 148 additions and 0 deletions

View File

@ -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",
]

View File

@ -6,3 +6,6 @@ pub mod network;
#[cfg(feature = "time")]
pub mod time;
#[cfg(feature = "syslog")]
pub mod syslog;

View File

@ -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<u64>,
pub limit: Option<u64>,
pub since: Option<String>,
pub until: Option<String>,
pub service: Option<String>,
}
#[api]
#[derive(Clone, PartialEq, Serialize, Deserialize)]
/// Syslog line with line number.
pub struct SyslogLine {
/// Line number.
pub n: u64,
/// Line text.
pub t: String,
}

View File

@ -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<SyslogLine>), 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<SyslogLine> = 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))
}

View File

@ -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;