203 lines
7.5 KiB
Diff
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(×tamp),
|
|
+ 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?
|
|
-
|