From 0ce2ca0cfe650b88bccdd8e9b62a9b233e794f34 Mon Sep 17 00:00:00 2001 From: licunlong Date: Wed, 28 Jun 2023 16:35:55 +0800 Subject: [PATCH] fix: several fixes on logger 1. Also use our own logger to log message to console, now log4rs can be removed 2. remove 'rolling_file', as it can only save log file in 644, we can't set the mode 3. change 'console' to 'console-syslog', when users want to print message to console we will also record the message in syslog 4. check the returned value more --- core/bin/manager/config.rs | 4 +- libs/basic/src/logger.rs | 311 +++++++++++++++++++++++++------------ 3 files changed, 215 insertions(+), 109 deletions(-) diff --git a/core/bin/manager/config.rs b/core/bin/manager/config.rs index fc4c028..691b692 100644 --- a/core/bin/manager/config.rs +++ b/core/bin/manager/config.rs @@ -26,7 +26,7 @@ pub struct ManagerConfig { #[config(default = "info")] pub LogLevel: log::LevelFilter, - #[config(default = "console")] + #[config(default = "syslog")] pub LogTarget: String, #[config(default = "")] pub LogFile: String, @@ -57,7 +57,7 @@ impl Default for ManagerConfig { DefaultRestartSec: 100, DefaultTimeoutSec: 90, LogLevel: log::LevelFilter::Debug, - LogTarget: "console".to_string(), + LogTarget: "syslog".to_string(), LogFile: String::new(), LogFileSize: 10240, LogFileNumber: 10, diff --git a/libs/basic/src/logger.rs b/libs/basic/src/logger.rs index 06f4ff2..8ee3ffa 100644 --- a/libs/basic/src/logger.rs +++ b/libs/basic/src/logger.rs @@ -12,9 +12,9 @@ //! use std::{ - fs::{File, OpenOptions}, + fs::{self, File, OpenOptions}, io::Write, - os::unix::prelude::OpenOptionsExt, + os::unix::prelude::{OpenOptionsExt, PermissionsExt}, path::{Path, PathBuf}, sync::Mutex, }; @@ -37,6 +37,8 @@ use log4rs::{ use nix::libc; use time::UtcOffset; +use crate::Error; + /// sysmaster log parttern: /// /// ```rust,ignore @@ -65,6 +67,36 @@ impl log::Log for LogPlugin { } } +fn write_msg_common(writer: &mut impl Write, 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 = match now.format(&format) { + Err(_) => "[unknown time]".to_string(), + Ok(v) => v, + }; + + /* 1. Write time */ + if let Err(e) = writer.write(format!("{now} ").as_bytes()) { + println!("Failed to log time message: {e}"); + return; + } + + /* 2. Write module */ + if let Err(e) = writer.write((module.to_string() + " ").as_bytes()) { + println!("Failed to log module message: {e}"); + return; + } + + /* 3. Write message */ + if let Err(e) = writer.write((msg + "\n").as_bytes()) { + println!("Failed to log message: {e}"); + } +} + +fn write_msg_file(writer: &mut File, module: &str, msg: String) { + write_msg_common(writer, module, msg); +} + struct SysLogger; /* This is an extremely simple implementation, and only @@ -92,6 +124,25 @@ impl log::Log for SysLogger { fn flush(&self) {} } +struct ConsoleLogger; + +impl log::Log for ConsoleLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + let mut stdout = std::io::stdout(); + let module_path = match record.module_path() { + None => "unknown", + Some(v) => v, + }; + write_msg_common(&mut stdout, module_path, record.args().to_string()); + } + + fn flush(&self) {} +} + struct FileLogger { level: log::Level, file_path: PathBuf, @@ -113,30 +164,55 @@ impl log::Log for FileLogger { } 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; + let mut file = match self.file.lock() { + Err(_) => return, + Ok(v) => v, + }; + + let module_path = match record.module_path() { + None => "unknown", + Some(v) => v, + }; + write_msg_file(&mut file, module_path, record.args().to_string()); + current_size = match file.metadata() { + Err(_) => return, + Ok(v) => v.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); + let file = match self.file.lock() { + Err(_) => return, + Ok(v) => v, + }; + if let Err(e) = self.rotate() { + println!("Failed to rotate log file: {e}"); + } + if let Err(e) = file.set_len(0) { + println!("Failed to clear log file: {e}"); + } } } fn flush(&self) { - let mut file = self.file.lock().unwrap(); - let _ = file.flush(); + let mut file = match self.file.lock() { + Err(_) => return, + Ok(v) => v, + }; + if let Err(e) = file.flush() { + println!("Failed to flush log file: {e}"); + } } } impl FileLogger { fn file_open(file_path: &PathBuf, file_mode: u32) -> File { + /* Panic if we cann't open a log file. */ + let dir = file_path.parent().unwrap(); + if !dir.exists() { + fs::create_dir_all(dir).unwrap(); + } + fs::set_permissions(dir, fs::Permissions::from_mode(0o700)).unwrap(); OpenOptions::new() .write(true) .create(true) @@ -146,22 +222,6 @@ impl FileLogger { .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, @@ -180,49 +240,70 @@ impl FileLogger { } } - 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}"); + fn mv_file_in_dir(src: &str, dst: Option<&str>, dir: &Path) { + let src = dir.join(src); + if dst.is_none() { + if let Err(e) = fs::remove_file(src) { + println!("Failed to remove old log file: {e}"); + } return; } - - /* 2. Write module */ - if let Err(e) = file.write((module.to_string() + " ").as_bytes()) { - println!("Failed to log module message: {e}"); - return; + let dst = dir.join(dst.unwrap()); /* safe here */ + if let Err(e) = fs::rename(src, dst) { + println!("Failed to rotate log file: {e}"); } + } - /* 3. Write message */ - if let Err(e) = file.write((msg + "\n").as_bytes()) { - println!("Failed to log message: {e}"); + fn cp_file_in_dir(src: &str, dst: &str, dir: &Path) { + let src = dir.join(src); + let dst = dir.join(dst); + if let Err(e) = fs::copy(src, &dst) { + println!("Failed to create sysmaster.log.1: {e}"); + } + if let Err(e) = fs::set_permissions(dst, fs::Permissions::from_mode(0o400)) { + println!("Failed to set log file mode: {e}"); } } - fn rotate(&self) { - let dir = self.file_path.parent().unwrap(); - let file_name = self - .file_path - .file_name() - .unwrap() - .to_string_lossy() - .to_string(); + fn rotate(&self) -> Result<(), Error> { + let dir = match self.file_path.parent() { + None => { + return Err(Error::Other { + msg: "Cannot determine the parent directory of log file".to_string(), + }) + } + Some(v) => v, + }; + let file_name = match self.file_path.file_name() { + None => { + return Err(Error::Other { + msg: "Cannot determine the file name of log file".to_string(), + }) + } + Some(v) => v.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() { + /* Walk through the parent directory, save the suffix rotate number in num_list */ + let mut num_list: Vec = Vec::new(); + let read_dir = match dir.read_dir() { + Err(e) => return Err(Error::Io { source: e }), + Ok(v) => v, + }; + for de in read_dir { let de = match de { Err(_) => continue, Ok(v) => v, }; - if !de.file_type().unwrap().is_file() { + + let file_type = match de.file_type() { + Err(_) => continue, + Ok(v) => v, + }; + if !file_type.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::() { @@ -238,17 +319,16 @@ impl FileLogger { /* 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 rotated_num in num_list.iter().skip(file_number - 1) { - let src = String::from(&file_name_dot) + &rotated_num.to_string(); + while num_list.len() > (self.file_number - 1) as usize { + let num = num_list.pop().unwrap(); /* safe here */ + let src = String::from(&file_name_dot) + &num.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(); + while let Some(num) = num_list.pop() { + let src = String::from(&file_name_dot) + &num.to_string(); + let dst = String::from(&file_name_dot) + &(num + 1).to_string(); Self::mv_file_in_dir(&src, Some(&dst), dir); } @@ -256,37 +336,38 @@ impl FileLogger { let src = String::from(&file_name); let dst = String::from(&file_name_dot) + "1"; Self::cp_file_in_dir(&src, &dst, dir); + Ok(()) } } -fn append_log( - app_name: &str, - level: LevelFilter, - target: &str, - file_path: &str, - file_size: u32, - file_number: u32, -) { - if target == "syslog" { - let _ = log::set_boxed_logger(Box::new(SysLogger)); - log::set_max_level(level); - return; +struct CombinedLogger { + loggers: Vec>, +} + +impl log::Log for CombinedLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true } - 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; + + fn log(&self, record: &log::Record) { + for logger in &self.loggers { + logger.log(record); + } + } + + fn flush(&self) {} +} + +impl CombinedLogger { + fn empty() -> Self { + Self { + loggers: Vec::new(), + } + } + + fn push(&mut self, logger: Box) { + self.loggers.push(logger) } - 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); - let _ = log::set_boxed_logger(Box::new(LogPlugin(logger))); } /// Init and set the sub unit manager's log @@ -297,7 +378,11 @@ fn append_log( /// /// target: log target /// -/// file_path: file path if the target is set to file +/// file_path: file path (valid when target == "file") +/// +/// file_size: the maximum size of an active log file (valid when target == "file") +/// +/// file_number: the maximum number of rotated log files (valid when target == "file") pub fn init_log_for_subum( app_name: &str, level: LevelFilter, @@ -309,7 +394,7 @@ pub fn init_log_for_subum( /* We should avoid calling init_log here, or we will get many "attempted * to set a logger after the logging system was already initialized" error * message. */ - append_log(app_name, level, target, file_path, file_size, file_number); + init_log(app_name, level, target, file_path, file_size, file_number); } /// Init and set the log target to console @@ -318,7 +403,7 @@ pub fn init_log_for_subum( /// /// level: maximum log level pub fn init_log_to_console(app_name: &str, level: LevelFilter) { - init_log(app_name, level, "console", "", 0, 0); + init_log(app_name, level, "console-syslog", "", 0, 0); } /// Init and set the log target to file @@ -327,7 +412,11 @@ pub fn init_log_to_console(app_name: &str, level: LevelFilter) { /// /// level: maximum log level /// -/// file_path: log to which file +/// file_path: file path +/// +/// file_size: the maximum size of an active log file +/// +/// file_number: the maximum number of rotated log files pub fn init_log_to_file( app_name: &str, level: LevelFilter, @@ -346,20 +435,42 @@ pub fn init_log_to_file( /// /// target: log target /// -/// file_path: file path if the target is set to file +/// file_path: file path (valid when target == "file") +/// +/// file_size: the maximum size of an active log file (valid when target == "file") +/// +/// file_number: the maximum number of rotated log files (valid when target == "file") pub fn init_log( - app_name: &str, + _app_name: &str, level: LevelFilter, target: &str, file_path: &str, file_size: u32, file_number: u32, ) { + let mut target = target; + if target == "file" && (file_path.is_empty() || file_size == 0 || file_number == 0) { + println!( + "LogTarget is configured to `file`, but configuration is invalid, changing the \ + LogTarget to `console`, file: {file_path}, file_size: {file_size}, file_number: \ + {file_number}" + ); + target = "syslog"; + } + if target == "syslog" { let _ = log::set_boxed_logger(Box::new(SysLogger)); log::set_max_level(level); return; } + if target == "console-syslog" { + let mut logger = CombinedLogger::empty(); + logger.push(Box::new(ConsoleLogger)); + logger.push(Box::new(SysLogger)); + let _ = log::set_boxed_logger(Box::new(logger)); + log::set_max_level(level); + return; + } if target == "file" { let _ = log::set_boxed_logger(Box::new(FileLogger::new( log::Level::Debug, @@ -371,13 +482,9 @@ pub fn init_log( 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 { - println!("{e}"); - } } +#[allow(unused)] fn build_log_config( app_name: &str, level: LevelFilter, -- 2.33.0