support_kit/logs/
logger_config.rs

1use serde::{Deserialize, Serialize};
2
3use crate::Configuration;
4
5use super::{LogFileConfig, LogLevel, LogLevelConfig, LogTarget, LoggerConfigOrPreset, Logging};
6
7#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, bon::Builder)]
8#[serde(rename_all = "kebab-case")]
9pub struct LoggerConfig {
10    console: Option<LogTarget>,
11    #[serde(flatten)]
12    #[builder(into)]
13    file: Option<LogFileConfig>,
14    #[builder(into)]
15    level: LogLevelConfig,
16}
17
18impl LoggerConfig {
19    pub fn level_range(&self) -> std::ops::Range<LogLevel> {
20        self.level.min_level()..self.level.max_level()
21    }
22
23    pub fn min_tracing_level(&self) -> tracing::Level {
24        self.level.min_level().tracing_level()
25    }
26
27    pub fn max_tracing_level(&self) -> tracing::Level {
28        self.level.max_level().tracing_level()
29    }
30
31    pub fn with_console_target(mut self, console: LogTarget) -> Self {
32        self.console = Some(console);
33        self
34    }
35
36    pub fn initialize(&self, config: &Configuration, logging: &mut Logging) {
37        match &self.file {
38            Some(file_config) => {
39                let (logger, guard) = file_config.init_log_appender(&self);
40
41                logging.loggers.push(logger);
42                logging.guards.push(guard);
43            }
44            _ => {}
45        }
46
47        match &self.console {
48            Some(console_target) => {
49                let logger = console_target.init_console_logger(config, &self);
50
51                logging.loggers.push(logger);
52            }
53            _ => {}
54        }
55    }
56}
57
58impl From<LoggerConfig> for LoggerConfigOrPreset {
59    fn from(logger_config: LoggerConfig) -> Self {
60        Self::Config(logger_config)
61    }
62}
63
64impl From<LoggerConfigOrPreset> for LoggerConfig {
65    fn from(config_or_preset: LoggerConfigOrPreset) -> Self {
66        match config_or_preset {
67            LoggerConfigOrPreset::Config(logger_config) => logger_config.into(),
68            LoggerConfigOrPreset::Preset(logging_preset) => logging_preset.into(),
69        }
70    }
71}
72
73#[test]
74fn single_logger_level_notation() -> Result<(), Box<dyn std::error::Error>> {
75    use super::LogLevel;
76
77    let config: LoggerConfig = serde_json::from_str(
78        r#"
79        {
80            "directory": "logs",
81            "level": "warn",
82            "name": "app.error"
83        }
84        "#,
85    )?;
86
87    assert_eq!(
88        config,
89        LoggerConfig::builder()
90            .level(LogLevel::Warn)
91            .file(("logs", "app.error"))
92            .build()
93    );
94
95    Ok(())
96}
97
98#[test]
99fn min_max_level_notation() -> Result<(), Box<dyn std::error::Error>> {
100    use super::{LogLevel, LogRotation};
101
102    let config: LoggerConfig = serde_json::from_str(
103        r#"
104        {
105            "directory": "logs",
106            "level": { "min": "info", "max": "warn" },
107            "name": "app",
108            "rotation": "daily"
109        }
110        "#,
111    )?;
112
113    assert_eq!(
114        config,
115        LoggerConfig::builder()
116            .level(LogLevel::Info..LogLevel::Warn)
117            .file(("logs", "app", LogRotation::Daily))
118            .build()
119    );
120
121    Ok(())
122}
123
124#[test]
125fn per_minute_rotation() -> Result<(), Box<dyn std::error::Error>> {
126    use super::{LogLevel, LogRotation};
127
128    let config: LoggerConfig = serde_json::from_str(
129        r#"
130        {
131            "directory": "logs",
132            "level": { "min": "trace", "max": "info" },
133            "name": "app.debug",
134            "rotation": "per-minute"
135        }
136        "#,
137    )?;
138
139    assert_eq!(
140        config,
141        LoggerConfig::builder()
142            .level(LogLevel::Trace..LogLevel::Info)
143            .file(("logs", "app.debug", LogRotation::PerMinute))
144            .build()
145    );
146
147    Ok(())
148}
149
150#[test]
151fn determining_log_levels() -> Result<(), Box<dyn std::error::Error>> {
152    use super::{LogLevel, LoggerConfig};
153
154    let error_logger: LoggerConfig = serde_json::from_str(
155        r#"
156        {
157            "directory": "logs",
158            "level": { "min": "info", "max": "warn" },
159            "name": "app",
160            "rotation": "daily"
161        }
162        "#,
163    )?;
164
165    assert_eq!(error_logger.level_range(), LogLevel::Info..LogLevel::Warn);
166
167    let debug_logger: LoggerConfig = serde_json::from_str(
168        r#"
169        {
170            "directory": "logs",
171            "level": { "min": "trace", "max": "info" },
172            "name": "app.debug",
173            "rotation": "per-minute"
174        }
175        "#,
176    )?;
177
178    assert_eq!(debug_logger.level_range(), LogLevel::Trace..LogLevel::Info);
179
180    Ok(())
181}
182
183#[test]
184fn constructing_loggers() -> Result<(), Box<dyn std::error::Error>> {
185    use super::{LogLevel, LogRotation, LoggerConfig, LoggingConfig};
186
187    let config: LoggingConfig = serde_json::from_str(
188        r#"
189        {
190            "directory": "logs",
191            "level": "warn",
192            "name": "app.debug",
193            "rotation": "per-minute"
194        }
195        "#,
196    )?;
197
198    let loggers = config.loggers();
199
200    assert_eq!(loggers.len(), 1);
201    assert_eq!(
202        loggers,
203        vec![LoggerConfig::builder()
204            .level(LogLevel::Warn)
205            .file(("logs", "app.debug", LogRotation::PerMinute))
206            .build()]
207    );
208
209    Ok(())
210}
211
212#[test]
213fn constructing_loggers_from_logger_config() -> Result<(), Box<dyn std::error::Error>> {
214    use super::{LogLevel, LoggerConfig};
215
216    let config: LoggerConfig = serde_json::from_str(
217        r#"
218        {
219            "directory": "logs",
220            "level": "warn",
221            "name": "app.error"
222        }
223        "#,
224    )?;
225
226    assert_eq!(
227        config,
228        LoggerConfig::builder()
229            .level(LogLevel::Warn)
230            .file(("logs", "app.error"))
231            .build()
232    );
233
234    Ok(())
235}