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