diff --git a/local/patches/base/P55-logd-json-format.patch b/local/patches/base/P55-logd-json-format.patch new file mode 100644 index 0000000000..d204bf68ec --- /dev/null +++ b/local/patches/base/P55-logd-json-format.patch @@ -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 { ++ 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(×tamp), ++ 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::(); + + 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(); + } diff --git a/recipes/core/base/P55-logd-json-format.patch b/recipes/core/base/P55-logd-json-format.patch new file mode 120000 index 0000000000..807bacbc28 --- /dev/null +++ b/recipes/core/base/P55-logd-json-format.patch @@ -0,0 +1 @@ +../../../local/patches/base/P55-logd-json-format.patch \ No newline at end of file diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 39549d21e1..5d8d3ae74e 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -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]