use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum LogFormat {
#[default]
Text,
Json,
}
impl std::fmt::Display for LogFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text => write!(f, "text"),
Self::Json => write!(f, "json"),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LogConfig {
#[serde(default)]
pub level: LogLevel,
#[serde(default)]
pub format: LogFormat,
#[serde(default = "default_true")]
pub console: bool,
#[serde(default)]
pub file: Option<String>,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: LogLevel::default(),
format: LogFormat::default(),
console: true,
file: None,
}
}
}
fn default_true() -> bool {
true
}
#[derive(
Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Trace,
Debug,
#[default]
Info,
Warn,
Error,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Trace => write!(f, "trace"),
Self::Debug => write!(f, "debug"),
Self::Info => write!(f, "info"),
Self::Warn => write!(f, "warn"),
Self::Error => write!(f, "error"),
}
}
}
impl LogLevel {
pub fn should_log(&self, min_level: LogLevel) -> bool {
*self >= min_level
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serde_yaml;
#[test]
fn test_parse_log_config_full() {
let yaml = r#"
level: debug
console: false
file: ./logs/workflow.log
"#;
let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.level, LogLevel::Debug);
assert!(!config.console);
assert_eq!(config.file, Some("./logs/workflow.log".to_string()));
}
#[test]
fn test_parse_log_config_minimal() {
let yaml = "level: warn";
let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.level, LogLevel::Warn);
assert!(config.console); assert_eq!(config.file, None);
}
#[test]
fn test_log_config_defaults() {
let config = LogConfig::default();
assert_eq!(config.level, LogLevel::Info);
assert_eq!(config.format, LogFormat::Text);
assert!(config.console);
assert_eq!(config.file, None);
}
#[test]
fn test_log_level_ordering() {
assert!(LogLevel::Debug < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Warn);
assert!(LogLevel::Warn < LogLevel::Error);
}
#[test]
fn test_log_level_should_log() {
let min = LogLevel::Warn;
assert!(!LogLevel::Debug.should_log(min));
assert!(!LogLevel::Info.should_log(min));
assert!(LogLevel::Warn.should_log(min));
assert!(LogLevel::Error.should_log(min));
}
#[test]
fn test_log_level_display() {
assert_eq!(LogLevel::Trace.to_string(), "trace");
assert_eq!(LogLevel::Debug.to_string(), "debug");
assert_eq!(LogLevel::Info.to_string(), "info");
assert_eq!(LogLevel::Warn.to_string(), "warn");
assert_eq!(LogLevel::Error.to_string(), "error");
}
#[test]
fn test_log_format_display() {
assert_eq!(LogFormat::Text.to_string(), "text");
assert_eq!(LogFormat::Json.to_string(), "json");
}
#[test]
fn test_parse_log_format_json() {
let yaml = r#"
level: debug
format: json
console: true
"#;
let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.format, LogFormat::Json);
}
#[test]
fn test_parse_log_level_trace() {
let yaml = "level: trace";
let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
assert_eq!(config.level, LogLevel::Trace);
}
#[test]
fn test_trace_should_log() {
assert!(LogLevel::Trace.should_log(LogLevel::Trace));
assert!(!LogLevel::Trace.should_log(LogLevel::Debug));
assert!(!LogLevel::Trace.should_log(LogLevel::Info));
}
#[test]
fn test_log_config_with_template() {
let yaml = r#"
level: info
file: ./logs/{{context.meta.workflow}}-{{context.meta.date}}.log
"#;
let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
assert!(config
.file
.as_ref()
.unwrap()
.contains("context.meta.workflow"));
assert!(config.file.as_ref().unwrap().contains("context.meta.date"));
}
}