support_kit/logs/
logger_config.rs1use 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}