// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. // // StratoVirt is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. // You may obtain a copy of Mulan PSL v2 at: // http://license.coscl.org.cn/MulanPSL2 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. use std::fs::File; use std::io::Write; use std::num::Wrapping; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use std::sync::Mutex; use std::time::UNIX_EPOCH; use anyhow::{Context, Result}; use log::{Level, LevelFilter, Log, Metadata, Record}; use crate::time::{get_format_time, gettime}; use crate::unix::gettid; // Max size of the log file is 100MB. const LOG_ROTATE_SIZE_MAX: usize = 100 * 1024 * 1024; // Logs are retained for seven days. const LOG_ROTATE_COUNT_MAX: u32 = 7; fn format_now() -> String { let (sec, nsec) = gettime(); let format_time = get_format_time(sec as i64); format!( "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}", format_time[0], format_time[1], format_time[2], format_time[3], format_time[4], format_time[5], nsec ) } struct FileRotate { handler: Box<dyn Write + Send>, path: String, current_size: Wrapping<usize>, create_day: i32, } impl FileRotate { fn rotate_file(&mut self, size_inc: usize) -> Result<()> { if self.path.is_empty() { return Ok(()); } self.current_size += Wrapping(size_inc); let sec = gettime().0; let today = get_format_time(sec as i64)[2]; if self.current_size < Wrapping(LOG_ROTATE_SIZE_MAX) && self.create_day == today { return Ok(()); } // Remove the oldest log file. let mut rotate_count = LOG_ROTATE_COUNT_MAX - 1; let old_name = format!("{}{}", self.path, rotate_count); if Path::new(&old_name).exists() { std::fs::remove_file(&old_name) .with_context(|| format! {"Failed to remove log file {}", old_name})?; } // Rename files to older file name. let mut path_from; let mut path_to = old_name; while rotate_count != 0 { rotate_count -= 1; path_from = self.path.clone(); if rotate_count != 0 { path_from += &rotate_count.to_string(); } if Path::new(&path_from).exists() { std::fs::rename(&path_from, &path_to).with_context( || format! {"Failed to rename log file from {} to {}", path_from, path_to}, )?; } path_to = path_from; } // Update log file. self.handler = Box::new(open_log_file(&self.path)?); self.current_size = Wrapping(0); self.create_day = today; Ok(()) } } /// Format like "%year-%mon-%dayT%hour:%min:%sec.%nsec struct VmLogger { rotate: Mutex<FileRotate>, level: Level, } impl Log for VmLogger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= self.level } fn log(&self, record: &Record) { if !self.enabled(record.metadata()) { return; } let pid = unsafe { libc::getpid() }; let tid = gettid(); let formatmsg = format_args!( "{:<5}: [{}][{}][{}: {}]:{}: {}\n", format_now(), pid, tid, record.file().unwrap_or(""), record.line().unwrap_or(0), record.level(), record.args() ) .to_string(); let mut rotate = self.rotate.lock().unwrap(); if let Err(e) = rotate.handler.write_all(formatmsg.as_bytes()) { println!("Failed to log message {:?}", e); return; } if let Err(e) = rotate.rotate_file(formatmsg.as_bytes().len()) { println!("Failed to rotate log files {:?}", e); } } fn flush(&self) {} } fn init_vm_logger( level: Level, logfile: Box<dyn Write + Send>, logfile_path: String, ) -> Result<()> { let current_size; let create_day; if logfile_path.is_empty() { current_size = Wrapping(0); create_day = 0; } else { let metadata = File::open(&logfile_path)?.metadata()?; current_size = Wrapping(metadata.len() as usize); let mod_time = metadata.modified()?; let sec = mod_time.duration_since(UNIX_EPOCH)?.as_secs(); create_day = get_format_time(sec as i64)[2]; }; let rotate = Mutex::new(FileRotate { handler: logfile, path: logfile_path, current_size, create_day, }); let logger = VmLogger { rotate, level }; log::set_boxed_logger(Box::new(logger)).map(|()| log::set_max_level(LevelFilter::Trace))?; Ok(()) } fn init_logger_with_env(logfile: Box<dyn Write + Send>, logfile_path: String) -> Result<()> { let level = match std::env::var("STRATOVIRT_LOG_LEVEL") { Ok(l) => match l.to_lowercase().as_str() { "error" => Level::Error, "warn" => Level::Warn, "info" => Level::Info, "debug" => Level::Debug, "trace" => Level::Trace, _ => Level::Info, }, _ => Level::Info, }; init_vm_logger(level, logfile, logfile_path)?; Ok(()) } fn open_log_file(path: &str) -> Result<File> { std::fs::OpenOptions::new() .read(false) .write(true) .append(true) .create(true) .mode(0o640) .open(path) .with_context(|| format!("Failed to open log file {}", path)) } pub fn init_log(path: String) -> Result<()> { let logfile: Box<dyn Write + Send> = if path.is_empty() { Box::new(std::io::stderr()) } else { Box::new(open_log_file(&path)?) }; init_logger_with_env(logfile, path.clone()) .with_context(|| format!("Failed to init logger: {}", path)) }