1use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq, Hash)]
31#[serde(rename_all = "lowercase")]
32pub enum LogFormat {
33 #[default]
35 Text,
36
37 Json,
39}
40
41impl std::fmt::Display for LogFormat {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Self::Text => write!(f, "text"),
45 Self::Json => write!(f, "json"),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Deserialize, Serialize)]
56pub struct LogConfig {
57 #[serde(default)]
59 pub level: LogLevel,
60
61 #[serde(default)]
63 pub format: LogFormat,
64
65 #[serde(default = "default_true")]
67 pub console: bool,
68
69 #[serde(default)]
71 pub file: Option<String>,
72}
73
74impl Default for LogConfig {
75 fn default() -> Self {
76 Self {
77 level: LogLevel::default(),
78 format: LogFormat::default(),
79 console: true,
80 file: None,
81 }
82 }
83}
84
85fn default_true() -> bool {
86 true
87}
88
89#[derive(
98 Debug, Clone, Copy, Deserialize, Serialize, Default, PartialEq, Eq, PartialOrd, Ord, Hash,
99)]
100#[serde(rename_all = "lowercase")]
101pub enum LogLevel {
102 Trace,
104
105 Debug,
107
108 #[default]
110 Info,
111
112 Warn,
114
115 Error,
117}
118
119impl std::fmt::Display for LogLevel {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
122 Self::Trace => write!(f, "trace"),
123 Self::Debug => write!(f, "debug"),
124 Self::Info => write!(f, "info"),
125 Self::Warn => write!(f, "warn"),
126 Self::Error => write!(f, "error"),
127 }
128 }
129}
130
131impl LogLevel {
132 pub fn should_log(&self, min_level: LogLevel) -> bool {
144 *self >= min_level
145 }
146}
147
148#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::serde_yaml;
156
157 #[test]
158 fn test_parse_log_config_full() {
159 let yaml = r#"
160level: debug
161console: false
162file: ./logs/workflow.log
163"#;
164 let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
165 assert_eq!(config.level, LogLevel::Debug);
166 assert!(!config.console);
167 assert_eq!(config.file, Some("./logs/workflow.log".to_string()));
168 }
169
170 #[test]
171 fn test_parse_log_config_minimal() {
172 let yaml = "level: warn";
173 let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
174 assert_eq!(config.level, LogLevel::Warn);
175 assert!(config.console); assert_eq!(config.file, None);
177 }
178
179 #[test]
180 fn test_log_config_defaults() {
181 let config = LogConfig::default();
182 assert_eq!(config.level, LogLevel::Info);
183 assert_eq!(config.format, LogFormat::Text);
184 assert!(config.console);
185 assert_eq!(config.file, None);
186 }
187
188 #[test]
189 fn test_log_level_ordering() {
190 assert!(LogLevel::Debug < LogLevel::Info);
191 assert!(LogLevel::Info < LogLevel::Warn);
192 assert!(LogLevel::Warn < LogLevel::Error);
193 }
194
195 #[test]
196 fn test_log_level_should_log() {
197 let min = LogLevel::Warn;
198
199 assert!(!LogLevel::Debug.should_log(min));
200 assert!(!LogLevel::Info.should_log(min));
201 assert!(LogLevel::Warn.should_log(min));
202 assert!(LogLevel::Error.should_log(min));
203 }
204
205 #[test]
206 fn test_log_level_display() {
207 assert_eq!(LogLevel::Trace.to_string(), "trace");
208 assert_eq!(LogLevel::Debug.to_string(), "debug");
209 assert_eq!(LogLevel::Info.to_string(), "info");
210 assert_eq!(LogLevel::Warn.to_string(), "warn");
211 assert_eq!(LogLevel::Error.to_string(), "error");
212 }
213
214 #[test]
215 fn test_log_format_display() {
216 assert_eq!(LogFormat::Text.to_string(), "text");
217 assert_eq!(LogFormat::Json.to_string(), "json");
218 }
219
220 #[test]
221 fn test_parse_log_format_json() {
222 let yaml = r#"
223level: debug
224format: json
225console: true
226"#;
227 let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
228 assert_eq!(config.format, LogFormat::Json);
229 }
230
231 #[test]
232 fn test_parse_log_level_trace() {
233 let yaml = "level: trace";
234 let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
235 assert_eq!(config.level, LogLevel::Trace);
236 }
237
238 #[test]
239 fn test_trace_should_log() {
240 assert!(LogLevel::Trace.should_log(LogLevel::Trace));
242 assert!(!LogLevel::Trace.should_log(LogLevel::Debug));
243 assert!(!LogLevel::Trace.should_log(LogLevel::Info));
244 }
245
246 #[test]
247 fn test_log_config_with_template() {
248 let yaml = r#"
249level: info
250file: ./logs/{{context.meta.workflow}}-{{context.meta.date}}.log
251"#;
252 let config: LogConfig = serde_yaml::from_str(yaml).unwrap();
253 assert!(config
254 .file
255 .as_ref()
256 .unwrap()
257 .contains("context.meta.workflow"));
258 assert!(config.file.as_ref().unwrap().contains("context.meta.date"));
259 }
260}