Files
RedBear-OS/local/patches/base/P61-logd-graceful-early-boot.patch
T

203 lines
7.5 KiB
Diff

diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs
index 070de3d6..f5c1549a 100644
--- a/logd/src/scheme.rs
+++ b/logd/src/scheme.rs
@@ -1,2 +1,2 @@
-use std::collections::{BTreeMap, VecDeque};
-use std::fs::{File, OpenOptions};
+use std::collections::{BTreeMap, HashMap, VecDeque};
+use std::fs::{File, OpenOptions, rename};
@@ -5,0 +6 @@ use std::os::fd::{FromRawFd, RawFd};
+use std::path::PathBuf;
@@ -6,0 +8 @@ use std::sync::mpsc::{self, Sender};
+use std::time::{SystemTime, UNIX_EPOCH};
@@ -13,0 +16,5 @@ use syscall::schemev2::NewFdFlags;
+const LOG_DIR: &str = "/var/log";
+const MAX_LOG_SIZE: u64 = 10 * 1024 * 1024;
+const MAX_ROTATED_FILES: u32 = 5;
+const MEMORY_LOG_LIMIT: usize = 1000;
+
@@ -31 +38 @@ enum OutputCmd {
- Log(Vec<u8>),
+ Log { context: String, line: Vec<u8> },
@@ -34,0 +42,96 @@ enum OutputCmd {
+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,
+ bytes_written: u64,
+}
+
+impl LogFile {
+ fn open(path: PathBuf) -> std::io::Result<Self> {
+ let file = OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&path)?;
+ let metadata = file.metadata()?;
+ Ok(LogFile {
+ file,
+ path,
+ bytes_written: metadata.len(),
+ })
+ }
+
+ fn write(&mut self, data: &[u8]) -> std::io::Result<()> {
+ self.file.write_all(data)?;
+ self.file.flush()?;
+ self.bytes_written += data.len() as u64;
+ Ok(())
+ }
+
+ fn maybe_rotate(&mut self) -> std::io::Result<()> {
+ if self.bytes_written < MAX_LOG_SIZE {
+ return Ok(());
+ }
+
+ drop(std::mem::replace(&mut self.file, unsafe { File::from_raw_fd(-1) }));
+
+ for i in (1..MAX_ROTATED_FILES).rev() {
+ let old_path = self.path.with_extension(format!("log.{}", i));
+ let new_path = self.path.with_extension(format!("log.{}", i + 1));
+ let _ = rename(&old_path, &new_path);
+ }
+
+ let backup_path = self.path.with_extension("log.1");
+ let _ = rename(&self.path, &backup_path);
+
+ self.file = OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&self.path)?;
+ self.bytes_written = 0;
+ Ok(())
+ }
+}
+
@@ -43,0 +147,13 @@ impl<'sock> LogScheme<'sock> {
+ let _ = std::fs::create_dir_all("/var/log");
+ let persistent_log: Option<File> = OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open("/var/log/system.log")
+ .ok();
+ let mut service_logs: HashMap<String, LogFile> = HashMap::new();
+
+ let _ = std::fs::create_dir_all(LOG_DIR);
+
+
+ let json_format = std::env::var("LOGD_JSON").map_or(false, |v| v == "1");
+
@@ -48,0 +165,5 @@ impl<'sock> LogScheme<'sock> {
+ let mut persistent = persistent_log;
+ if let Some(ref mut f) = persistent {
+ let _ = f.write(b"--- logd started ---
+");
+ }
@@ -51 +172,32 @@ impl<'sock> LogScheme<'sock> {
- OutputCmd::Log(line) => {
+ 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(&out_line);
+ let _ = f.flush();
+ }
+
+ let service_name = context.split(':').next().unwrap_or("unknown");
+ if !service_name.is_empty() {
+ let log_path = PathBuf::from(LOG_DIR).join(format!("{}.log", service_name));
+ let entry = service_logs.entry(service_name.to_string()).or_insert_with(|| {
+ LogFile::open(log_path).unwrap_or_else(|_| {
+ LogFile::open(PathBuf::from("/dev/null")).unwrap()
+ })
+ });
+ let _ = entry.write(&out_line);
+ let _ = entry.maybe_rotate();
+ }
+
+ let system_path = PathBuf::from(LOG_DIR).join("system.log");
+ let system_entry = service_logs.entry("system".to_string()).or_insert_with(|| {
+ LogFile::open(system_path).unwrap_or_else(|_| {
+ LogFile::open(PathBuf::from("/dev/null")).unwrap()
+ })
+ });
+ let _ = system_entry.write(&out_line);
+ let _ = system_entry.maybe_rotate();
+
@@ -53 +205 @@ impl<'sock> LogScheme<'sock> {
- let _ = file.write(&line);
+ let _ = file.write(&out_line);
@@ -56,3 +208,2 @@ impl<'sock> LogScheme<'sock> {
- logs.push_back(line);
- // Keep a limited amount of logs for backfilling to bound memory usage
- while logs.len() > 1000 {
+ logs.push_back(out_line);
+ while logs.len() > MEMORY_LOG_LIMIT {
@@ -68 +218,0 @@ impl<'sock> LogScheme<'sock> {
-
@@ -83 +232,0 @@ impl<'sock> LogScheme<'sock> {
- // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue
@@ -118 +266,0 @@ impl<'sock> LogScheme<'sock> {
- // Writing to the kernel debug log never blocks
@@ -124 +272,4 @@ impl<'sock> LogScheme<'sock> {
- .send(OutputCmd::Log(mem::take(handle_buf)))
+ .send(OutputCmd::Log {
+ context: context.to_string(),
+ line: mem::take(handle_buf),
+ })
@@ -173,3 +323,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> {
-
- // TODO
-
@@ -244,3 +391,0 @@ impl<'sock> SchemeSync for LogScheme<'sock> {
-
- //TODO: flush remaining data?
-