From f70fcf7c4389ee9eb81f62e8b763e095ed50aea9 Mon Sep 17 00:00:00 2001 From: licunlong Date: Tue, 27 Jun 2023 20:15:04 +0800 Subject: [PATCH] feature: use our own file logger This implements a simple file logger which supports log rorating. we set the log file mode to 600 forcely --- libs/basic/Cargo.toml | 1 + libs/basic/src/logger.rs | 203 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/libs/basic/Cargo.toml b/libs/basic/Cargo.toml index 7557d37..6ee7162 100644 --- a/libs/basic/Cargo.toml +++ b/libs/basic/Cargo.toml @@ -21,6 +21,7 @@ lazy_static = "1.4.0" bitflags = "1.3.2" pkg-config = "0.3" rand = "0.4.6" +time = {version = "=0.3.10", features = ["formatting", "macros"] } [dev-dependencies] libtests = { path = "../libtests" } diff --git a/libs/basic/src/logger.rs b/libs/basic/src/logger.rs index 56def43..3bf579b 100644 --- a/libs/basic/src/logger.rs +++ b/libs/basic/src/logger.rs @@ -11,6 +11,14 @@ // See the Mulan PSL v2 for more details. //! +use std::{ + fs::{File, OpenOptions}, + io::Write, + os::unix::prelude::OpenOptionsExt, + path::{Path, PathBuf}, + sync::Mutex, +}; + use log::{LevelFilter, Log}; use log4rs::{ append::{ @@ -27,6 +35,7 @@ use log4rs::{ encode::pattern::PatternEncoder, }; use nix::libc; +use time::UtcOffset; /// sysmaster log parttern: /// @@ -83,6 +92,174 @@ impl log::Log for SysLogger { fn flush(&self) {} } +struct FileLogger { + level: log::Level, + file_path: PathBuf, + #[allow(dead_code)] + file_mode: u32, + file_number: u32, + max_size: u32, + file: Mutex, +} + +impl log::Log for FileLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + let current_size: u32; + { + let mut file = self.file.lock().unwrap(); + self.write( + &mut file, + record.module_path().unwrap(), + record.args().to_string(), + ); + current_size = file.metadata().unwrap().len() as u32; + /* file is automatically unlocked. */ + } + if current_size > self.max_size { + self.rotate(); + let file = self.file.lock().unwrap(); + let _ = file.set_len(0); + } + } + + fn flush(&self) { + let mut file = self.file.lock().unwrap(); + let _ = file.flush(); + } +} + +impl FileLogger { + fn file_open(file_path: &PathBuf, file_mode: u32) -> File { + OpenOptions::new() + .write(true) + .create(true) + .append(true) + .mode(file_mode) + .open(file_path) + .unwrap() + } + + fn mv_file_in_dir(src: &str, dst: Option<&str>, dir: &Path) { + let src = dir.join(src); + if dst.is_none() { + let _ = std::fs::remove_file(src); + return; + } + let dst = dir.join(dst.unwrap()); + let _ = std::fs::rename(src, dst); + } + + fn cp_file_in_dir(src: &str, dst: &str, dir: &Path) { + let src = dir.join(src); + let dst = dir.join(dst); + let _ = std::fs::copy(src, dst); + } + + fn new( + level: log::Level, + file_path: PathBuf, + file_mode: u32, + max_size: u32, + file_number: u32, + ) -> Self { + let file = Self::file_open(&file_path, file_mode); + Self { + level, + file_path, + file_mode, + file_number, + max_size: max_size * 1024, + file: Mutex::new(file), + } + } + + fn write(&self, file: &mut File, module: &str, msg: String) { + let now = time::OffsetDateTime::now_utc().to_offset(UtcOffset::UTC); + let format = + time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"); + let now = now.format(&format).unwrap(); + + /* 1. Write time */ + if let Err(e) = file.write(format!("{now} ").as_bytes()) { + println!("Failed to log time message: {e}"); + return; + } + + /* 2. Write module */ + if let Err(e) = file.write((module.to_string() + " ").as_bytes()) { + println!("Failed to log module message: {e}"); + return; + } + + /* 3. Write message */ + if let Err(e) = file.write((msg + "\n").as_bytes()) { + println!("Failed to log message: {e}"); + return; + } + } + + fn rotate(&self) { + let dir = self.file_path.parent().unwrap(); + let file_name = self + .file_path + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + let file_name_dot = String::from(&file_name) + "."; + let mut num_list: Vec = Vec::new(); + + for de in dir.read_dir().unwrap() { + let de = match de { + Err(_) => continue, + Ok(v) => v, + }; + if !de.file_type().unwrap().is_file() { + continue; + } + let de_file_name = de.file_name().to_string_lossy().to_string(); + let rotated_num = de_file_name.trim_start_matches(&file_name_dot); + let rotated_num = match rotated_num.parse::() { + Err(_) => { + continue; + } + Ok(v) => v, + }; + num_list.push(rotated_num); + } + + num_list.sort(); + + /* 1. delete surplus rotated file */ + /* We only keep (file_number - 1) rotated files, because we will generate a new one later. */ + let file_number = self.file_number as usize; + for i in file_number - 1..num_list.len() { + let src = String::from(&file_name_dot) + &num_list[i].to_string(); + Self::mv_file_in_dir(&src, None, dir); + } + + let end = std::cmp::min(num_list.len(), file_number); + /* 2. {sysmaster.log.1, sysmaster.log.2, ...} => {sysmaster.log.2, sysmaster.log.3, ...} */ + for i in (0..end).rev() { + let src = String::from(&file_name_dot) + &num_list[i].to_string(); + let dst = String::from(&file_name_dot) + &(num_list[i] + 1).to_string(); + Self::mv_file_in_dir(&src, Some(&dst), dir); + } + + /* 3. **copy** sysmaster.log => sysmaster.log.1 */ + let src = String::from(&file_name); + let dst = String::from(&file_name_dot) + "1"; + Self::cp_file_in_dir(&src, &dst, dir); + } +} + fn append_log( app_name: &str, level: LevelFilter, @@ -96,6 +273,17 @@ fn append_log( log::set_max_level(level); return; } + if target == "file" { + log::set_max_level(level); + let _ = log::set_boxed_logger(Box::new(FileLogger::new( + log::Level::Debug, + PathBuf::from(file_path), + 0o600, + file_size, + file_number, + ))); + return; + } let config = build_log_config(app_name, level, target, file_path, file_size, file_number); let logger = log4rs::Logger::new(config); log::set_max_level(level); @@ -173,6 +361,17 @@ pub fn init_log( log::set_max_level(level); return; } + if target == "file" { + let _ = log::set_boxed_logger(Box::new(FileLogger::new( + log::Level::Debug, + PathBuf::from(&file_path), + 0o600, + file_size, + file_number, + ))); + log::set_max_level(level); + return; + } let config = build_log_config(app_name, level, target, file_path, file_size, file_number); let r = log4rs::init_config(config); if let Err(e) = r { @@ -190,7 +389,7 @@ fn build_log_config( ) -> Config { let mut target = target; /* If the file is configured to None, use console forcely. */ - if (file_path.is_empty() || file_size == 0 || file_number == 0) && target == "file" { + if (file_path.is_empty() || file_size == 0 || file_number == 0) && target == "rolling_file" { println!( "LogTarget is configured to `file`, but configuration is invalid, changing the \ LogTarget to `console`, file: {file_path}, file_size: {file_size}, file_number: \ @@ -206,7 +405,7 @@ fn build_log_config( .target(Target::Stdout) .build(), ), - "file" => { + "rolling_file" => { let pattern = file_path.to_string() + ".{}"; let policy = Box::new(CompoundPolicy::new( Box::new(SizeTrigger::new(file_size as u64 * 1024)), -- 2.33.0