1
0
mirror of https://github.com/containers/youki synced 2024-11-23 17:32:15 +01:00

Cleanup logger implementation

This commit is contained in:
Furisto 2021-12-11 20:38:06 +01:00
parent 69b5d76fe4
commit e7d0e90529
2 changed files with 18 additions and 48 deletions

@ -1,6 +1,4 @@
//! Default Youki Logger use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use log::{LevelFilter, Log, Metadata, Record}; use log::{LevelFilter, Log, Metadata, Record};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::borrow::Cow; use std::borrow::Cow;
@ -11,27 +9,13 @@ use std::str::FromStr;
pub static LOG_FILE: OnceCell<Option<File>> = OnceCell::new(); pub static LOG_FILE: OnceCell<Option<File>> = OnceCell::new();
const LOG_LEVEL_ENV_NAME: &str = "YOUKI_INTEGRATION_LOG_LEVEL"; const LOG_LEVEL_ENV_NAME: &str = "YOUKI_INTEGRATION_LOG_LEVEL";
const LOG_FORMAT_TEXT: &str = "text";
const LOG_FORMAT_JSON: &str = "json";
enum LogFormat {
Text,
Json,
}
/// If in debug mode, default level is debug to get maximum logging
#[cfg(debug_assertions)]
const DEFAULT_LOG_LEVEL: &str = "debug";
/// If not in debug mode, default level is warn to get important logs
#[cfg(not(debug_assertions))]
const DEFAULT_LOG_LEVEL: &str = "warn";
/// Initialize the logger, must be called before accessing the logger /// Initialize the logger, must be called before accessing the logger
/// Multiple parts might call this at once, but the actual initialization /// Multiple parts might call this at once, but the actual initialization
/// is done only once due to use of OnceCell /// is done only once due to use of OnceCell
pub fn init(log_file: Option<PathBuf>, log_format: Option<String>) -> Result<()> { pub fn init(log_file: Option<PathBuf>, debug: bool) -> Result<()> {
let level = detect_log_level(true).context("failed to parse log level")?; let level = detect_log_level(debug).context("failed to parse log level")?;
let format = detect_log_format(log_format).context("failed to detect log format")?;
let _ = LOG_FILE.get_or_init(|| -> Option<File> { let _ = LOG_FILE.get_or_init(|| -> Option<File> {
log_file.map(|path| { log_file.map(|path| {
OpenOptions::new() OpenOptions::new()
@ -43,7 +27,7 @@ pub fn init(log_file: Option<PathBuf>, log_format: Option<String>) -> Result<()>
}) })
}); });
let logger = IntegrationLogger::new(level.to_level(), format); let logger = IntegrationLogger::new(level.to_level());
log::set_boxed_logger(Box::new(logger)) log::set_boxed_logger(Box::new(logger))
.map(|()| log::set_max_level(level)) .map(|()| log::set_max_level(level))
.expect("set logger failed"); .expect("set logger failed");
@ -51,35 +35,27 @@ pub fn init(log_file: Option<PathBuf>, log_format: Option<String>) -> Result<()>
Ok(()) Ok(())
} }
fn detect_log_format(log_format: Option<String>) -> Result<LogFormat> {
match log_format.as_deref() {
None | Some(LOG_FORMAT_TEXT) => Ok(LogFormat::Text),
Some(LOG_FORMAT_JSON) => Ok(LogFormat::Json),
Some(unknown) => bail!("unknown log format: {}", unknown),
}
}
fn detect_log_level(is_debug: bool) -> Result<LevelFilter> { fn detect_log_level(is_debug: bool) -> Result<LevelFilter> {
let filter: Cow<str> = if is_debug { let filter: Cow<str> = if is_debug {
"debug".into() "debug".into()
} else if let Ok(level) = std::env::var(LOG_LEVEL_ENV_NAME) { } else if let Ok(level) = std::env::var(LOG_LEVEL_ENV_NAME) {
level.into() level.into()
} else { } else {
DEFAULT_LOG_LEVEL.into() "off".into()
}; };
Ok(LevelFilter::from_str(filter.as_ref())?) Ok(LevelFilter::from_str(filter.as_ref())?)
} }
struct IntegrationLogger { struct IntegrationLogger {
/// Indicates level up to which logs are to be printed /// Indicates level up to which logs are to be printed
level: Option<log::Level>, level: Option<log::Level>,
format: LogFormat,
} }
impl IntegrationLogger { impl IntegrationLogger {
/// Create new logger /// Create new logger
pub fn new(level: Option<log::Level>, format: LogFormat) -> Self { pub fn new(level: Option<log::Level>) -> Self {
Self { level, format } Self { level }
} }
} }
@ -97,10 +73,7 @@ impl Log for IntegrationLogger {
/// Function to carry out logging /// Function to carry out logging
fn log(&self, record: &Record) { fn log(&self, record: &Record) {
if self.enabled(record.metadata()) { if self.enabled(record.metadata()) {
let log_msg = match self.format { let log_msg =text_format(record);
LogFormat::Text => text_format(record),
LogFormat::Json => json_format(record),
};
// if log file is set, write to it, else write to stderr // if log file is set, write to it, else write to stderr
if let Some(mut log_file) = LOG_FILE.get().unwrap().as_ref() { if let Some(mut log_file) = LOG_FILE.get().unwrap().as_ref() {
let _ = writeln!(log_file, "{}", log_msg); let _ = writeln!(log_file, "{}", log_msg);
@ -120,15 +93,6 @@ impl Log for IntegrationLogger {
} }
} }
fn json_format(record: &log::Record) -> String {
serde_json::to_string(&serde_json::json!({
"level": record.level().to_string(),
"time": chrono::Local::now().to_rfc3339(),
"message": record.args(),
}))
.expect("serde::to_string with string keys will not fail")
}
fn text_format(record: &log::Record) -> String { fn text_format(record: &log::Record) -> String {
let log_msg = match (record.file(), record.line()) { let log_msg = match (record.file(), record.line()) {
(Some(file), Some(line)) => format!( (Some(file), Some(line)) => format!(

@ -17,14 +17,20 @@ use tests::cgroups;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(version = "0.0.1", author = "youki team")] #[clap(version = "0.0.1", author = "youki team")]
struct Opts { struct Opts {
/// path for the container runtime to be tested /// Path for the container runtime to be tested
#[clap(short, long)] #[clap(short, long)]
runtime: PathBuf, runtime: PathBuf,
/// selected tests to be run, format should be /// Selected tests to be run, format should be
/// space separated groups, eg /// space separated groups, eg
/// -t group1::test1,test3 group2 group3::test5 /// -t group1::test1,test3 group2 group3::test5
#[clap(short, long, multiple_values = true, value_delimiter = ' ')] #[clap(short, long, multiple_values = true, value_delimiter = ' ')]
tests: Option<Vec<String>>, tests: Option<Vec<String>>,
/// Enables debug output
#[clap(short, long)]
debug: bool,
/// Logs to the specified file
#[clap(long)]
log: Option<PathBuf>,
} }
// parse test string given in commandline option as pair of testgroup name and tests belonging to that // parse test string given in commandline option as pair of testgroup name and tests belonging to that
@ -45,7 +51,7 @@ fn parse_tests(tests: &[String]) -> Vec<(&str, Option<Vec<&str>>)> {
fn main() -> Result<()> { fn main() -> Result<()> {
let opts: Opts = Opts::parse(); let opts: Opts = Opts::parse();
if let Err(e) = logger::init(None, None) { if let Err(e) = logger::init(opts.log, opts.debug) {
eprintln!("logger could not be initialized: {:?}", e); eprintln!("logger could not be initialized: {:?}", e);
} }