]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/syslog.rs
replace Userid with Authid
[proxmox-backup.git] / src / api2 / node / syslog.rs
1 use std::process::{Command, Stdio};
2
3 use anyhow::{Error};
4 use serde_json::{json, Value};
5
6 use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
7
8 use crate::api2::types::*;
9 use crate::config::acl::PRIV_SYS_AUDIT;
10
11 fn dump_journal(
12 start: Option<u64>,
13 limit: Option<u64>,
14 since: Option<&str>,
15 until: Option<&str>,
16 service: Option<&str>,
17 ) -> Result<(u64, Vec<Value>), Error> {
18
19 let mut args = vec!["-o", "short", "--no-pager"];
20
21 if let Some(service) = service { args.extend(&["--unit", service]); }
22 if let Some(since) = since { args.extend(&["--since", since]); }
23 if let Some(until) = until { args.extend(&["--until", until]); }
24
25 let mut lines: Vec<Value> = vec![];
26 let mut limit = limit.unwrap_or(50);
27 let start = start.unwrap_or(0);
28 let mut count: u64 = 0;
29
30 let mut child = Command::new("journalctl")
31 .args(&args)
32 .stdout(Stdio::piped())
33 .spawn()?;
34
35 use std::io::{BufRead,BufReader};
36
37 if let Some(ref mut stdout) = child.stdout {
38 for line in BufReader::new(stdout).lines() {
39 match line {
40 Ok(line) => {
41 count += 1;
42 if count < start { continue };
43 if limit == 0 { continue };
44
45 lines.push(json!({ "n": count, "t": line }));
46
47 limit -= 1;
48 }
49 Err(err) => {
50 log::error!("reading journal failed: {}", err);
51 let _ = child.kill();
52 break;
53 }
54 }
55 }
56 }
57
58 let status = child.wait().unwrap();
59 if !status.success() {
60 log::error!("journalctl failed with {}", status);
61 }
62
63 // HACK: ExtJS store.guaranteeRange() does not like empty array
64 // so we add a line
65 if count == 0 {
66 count += 1;
67 lines.push(json!({ "n": count, "t": "no content"}));
68 }
69
70 Ok((count, lines))
71 }
72
73 #[api(
74 protected: true,
75 input: {
76 properties: {
77 node: {
78 schema: NODE_SCHEMA,
79 },
80 start: {
81 type: Integer,
82 description: "Start line number.",
83 minimum: 0,
84 optional: true,
85 },
86 limit: {
87 type: Integer,
88 description: "Max. number of lines.",
89 optional: true,
90 minimum: 0,
91 },
92 since: {
93 type: String,
94 optional: true,
95 description: "Display all log since this date-time string.",
96 format: &SYSTEMD_DATETIME_FORMAT,
97 },
98 until: {
99 type: String,
100 optional: true,
101 description: "Display all log until this date-time string.",
102 format: &SYSTEMD_DATETIME_FORMAT,
103 },
104 service: {
105 type: String,
106 optional: true,
107 description: "Service ID.",
108 max_length: 128,
109 },
110 },
111 },
112 returns: {
113 type: Object,
114 description: "Returns a list of syslog entries.",
115 properties: {
116 n: {
117 type: Integer,
118 description: "Line number.",
119 },
120 t: {
121 type: String,
122 description: "Line text.",
123 }
124 },
125 },
126 access: {
127 permission: &Permission::Privilege(&["system", "log"], PRIV_SYS_AUDIT, false),
128 },
129 )]
130 /// Read syslog entries.
131 fn get_syslog(
132 param: Value,
133 _info: &ApiMethod,
134 mut rpcenv: &mut dyn RpcEnvironment,
135 ) -> Result<Value, Error> {
136
137 let (count, lines) = dump_journal(
138 param["start"].as_u64(),
139 param["limit"].as_u64(),
140 param["since"].as_str(),
141 param["until"].as_str(),
142 param["service"].as_str())?;
143
144 rpcenv["total"] = Value::from(count);
145
146 Ok(json!(lines))
147 }
148
149 pub const ROUTER: Router = Router::new()
150 .get(&API_METHOD_GET_SYSLOG);
151