Add P55: JSON structured log format option to logd

When LOGD_JSON=1 is set in the environment, logd formats all log
lines as JSON objects with timestamp, source, and message fields.
Also fixes indentation issues in P51 logd rotation patch.
This commit is contained in:
2026-05-21 00:48:13 +03:00
parent 54a33a7a15
commit 5715f86dc6
3 changed files with 118 additions and 0 deletions
@@ -0,0 +1,115 @@
--- a/logd/src/scheme.rs
+++ b/logd/src/scheme.rs
@@ -5,6 +5,7 @@
use std::os::fd::{FromRawFd, RawFd};
use std::path::PathBuf;
use std::sync::mpsc::{self, Sender};
+use std::time::{SystemTime, UNIX_EPOCH};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket};
@@ -38,6 +39,50 @@
AddSink(usize),
}
+fn json_escape(s: &str) -> String {
+ let mut out = String::with_capacity(s.len());
+ for c in s.chars() {
+ match c {
+ '\\' => out.push_str("\\\\"),
+ '"' => out.push_str("\\\""),
+ '\n' => out.push_str("\\n"),
+ '\r' => out.push_str("\\r"),
+ '\t' => out.push_str("\\t"),
+ c if c.is_control() => out.push_str(&format!("\\u{:04x}", c as u32)),
+ c => out.push(c),
+ }
+ }
+ out
+}
+
+fn format_json_line(context: &str, line: &[u8]) -> Vec<u8> {
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap_or_default();
+ let secs = now.as_secs();
+ let timestamp = format!(
+ "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
+ 1970 + secs / 31_557_600,
+ (secs % 31_557_600) / 2_592_000 + 1,
+ (secs % 2_592_000) / 86_400 + 1,
+ (secs % 86_400) / 3600,
+ (secs % 3600) / 60,
+ secs % 60
+ );
+ let text = String::from_utf8_lossy(line).trim_end_matches('\n').to_string();
+ let (source, message) = match text.split_once(": ") {
+ Some((s, m)) => (s, m),
+ None => (context, text.as_str()),
+ };
+ let json = format!(
+ "{{\"timestamp\":\"{}\",\"source\":\"{}\",\"message\":\"{}\"}}\n",
+ json_escape(&timestamp),
+ json_escape(source),
+ json_escape(message)
+ );
+ json.into_bytes()
+}
+
struct LogFile {
file: File,
path: PathBuf,
@@ -110,6 +155,8 @@
let _ = std::fs::create_dir_all(LOG_DIR);
+ let json_format = std::env::var("LOGD_JSON").map_or(false, |v| v == "1");
+
let (output_tx, output_rx) = mpsc::channel::<OutputCmd>();
std::thread::spawn(move || {
@@ -123,9 +170,15 @@
for cmd in output_rx {
match cmd {
OutputCmd::Log { context, line } => {
+ let out_line = if json_format {
+ format_json_line(&context, &line)
+ } else {
+ line.clone()
+ };
if let Some(ref mut f) = persistent {
- let _ = f.write(&line);
+ let _ = f.write(&out_line);
let _ = f.flush();
+ }
let service_name = context.split(':').next().unwrap_or("unknown");
if !service_name.is_empty() {
@@ -135,7 +188,7 @@
LogFile::open(PathBuf::from("/dev/null")).unwrap()
})
});
- let _ = entry.write(&line);
+ let _ = entry.write(&out_line);
let _ = entry.maybe_rotate();
}
@@ -145,15 +198,14 @@
LogFile::open(PathBuf::from("/dev/null")).unwrap()
})
});
- let _ = system_entry.write(&line);
+ let _ = system_entry.write(&out_line);
let _ = system_entry.maybe_rotate();
- }
for file in &mut files {
- let _ = file.write(&line);
+ let _ = file.write(&out_line);
let _ = file.flush();
}
- logs.push_back(line);
+ logs.push_back(out_line);
while logs.len() > MEMORY_LOG_LIMIT {
logs.pop_front();
}
+1
View File
@@ -0,0 +1 @@
../../../local/patches/base/P55-logd-json-format.patch
+2
View File
@@ -111,6 +111,8 @@ patches = [
"P52-acpid-cstates.patch",
# P53: Add e1000d interrupt throttling rate (ITR) coalescing
"P53-e1000d-itr-coalescing.patch",
# P55: Add JSON structured log format option to logd
"P55-logd-json-format.patch",
]
[package]