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