From 0ce2ca0cfe650b88bccdd8e9b62a9b233e794f34 Mon Sep 17 00:00:00 2001
From: licunlong <licunlong1@huawei.com>
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<usize> = 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<usize> = 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::<usize>() {
@@ -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<Box<dyn log::Log>>,
+}
+
+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<dyn Log>) {
+        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

