Skip to main content

kellnr_settings/
log.rs

1use std::fmt::Display;
2
3use clap::ValueEnum;
4use provcfg::{ClapArgs, Configurable};
5use serde::{Deserialize, Deserializer, Serialize};
6
7#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Configurable, ClapArgs)]
8#[serde(default)]
9pub struct Log {
10    #[arg(long = "log-format", value_enum)]
11    pub format: LogFormat,
12    #[arg(long = "log-level", short = 'l', value_enum)]
13    pub level: LogLevel,
14    #[arg(long = "log-level-web-server", value_enum)]
15    pub level_web_server: LogLevel,
16}
17
18impl Default for Log {
19    fn default() -> Self {
20        Self {
21            format: LogFormat::Compact,
22            level: LogLevel::Info,
23            level_web_server: LogLevel::Warn,
24        }
25    }
26}
27
28#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum, Default)]
29pub enum LogFormat {
30    #[default]
31    Compact,
32    Pretty,
33    Json,
34}
35
36impl<'de> Deserialize<'de> for LogFormat {
37    fn deserialize<D>(de: D) -> Result<Self, D::Error>
38    where
39        D: Deserializer<'de>,
40    {
41        match String::deserialize(de)?.to_lowercase().as_str() {
42            "compact" => Ok(Self::Compact),
43            "pretty" => Ok(Self::Pretty),
44            "json" => Ok(Self::Json),
45            other => Err(serde::de::Error::custom(format!(
46                "unknown log format {other:?}; expected \"compact\", \"pretty\", or \"json\""
47            ))),
48        }
49    }
50}
51
52impl Serialize for LogFormat {
53    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
54    where
55        S: serde::Serializer,
56    {
57        serializer.serialize_str(&self.to_string())
58    }
59}
60
61impl Display for LogFormat {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            LogFormat::Compact => write!(f, "compact"),
65            LogFormat::Pretty => write!(f, "pretty"),
66            LogFormat::Json => write!(f, "json"),
67        }
68    }
69}
70
71#[derive(Debug, PartialEq, Eq, Clone, Copy, ValueEnum, Default)]
72pub enum LogLevel {
73    Trace,
74    Debug,
75    #[default]
76    Info,
77    Warn,
78    Error,
79}
80
81impl<'de> Deserialize<'de> for LogLevel {
82    fn deserialize<D>(de: D) -> Result<Self, D::Error>
83    where
84        D: Deserializer<'de>,
85    {
86        match String::deserialize(de)?.to_lowercase().as_str() {
87            "trace" => Ok(Self::Trace),
88            "debug" => Ok(Self::Debug),
89            "info" => Ok(Self::Info),
90            "warn" => Ok(Self::Warn),
91            "error" => Ok(Self::Error),
92            other => Err(serde::de::Error::custom(format!(
93                "unknown log level {other:?}; expected one of trace, debug, info, warn, error"
94            ))),
95        }
96    }
97}
98
99impl Display for LogLevel {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            LogLevel::Trace => write!(f, "trace"),
103            LogLevel::Debug => write!(f, "debug"),
104            LogLevel::Info => write!(f, "info"),
105            LogLevel::Warn => write!(f, "warn"),
106            LogLevel::Error => write!(f, "error"),
107        }
108    }
109}
110
111impl Serialize for LogLevel {
112    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
113    where
114        S: serde::Serializer,
115    {
116        serializer.serialize_str(&self.to_string())
117    }
118}
119
120impl From<LogLevel> for tracing::Level {
121    fn from(value: LogLevel) -> Self {
122        match value {
123            LogLevel::Trace => tracing::Level::TRACE,
124            LogLevel::Debug => tracing::Level::DEBUG,
125            LogLevel::Info => tracing::Level::INFO,
126            LogLevel::Warn => tracing::Level::WARN,
127            LogLevel::Error => tracing::Level::ERROR,
128        }
129    }
130}
131
132impl From<LogLevel> for tracing::level_filters::LevelFilter {
133    fn from(value: LogLevel) -> Self {
134        Self::from_level(value.into())
135    }
136}
137
138#[cfg(test)]
139mod log_format_tests {
140    use serde::Deserialize;
141
142    use super::*;
143
144    #[derive(Debug, Deserialize)]
145    struct Settings {
146        log_format: LogFormat,
147    }
148
149    #[test]
150    fn test_deserialize_log_format_compact() {
151        let toml = r#"
152            log_format = "compact"
153        "#;
154
155        let settings: Settings = toml::from_str(toml).unwrap();
156        assert_eq!(settings.log_format, LogFormat::Compact);
157    }
158
159    #[test]
160    fn test_deserialize_log_format_pretty() {
161        let toml = r#"
162            log_format = "pretty"
163        "#;
164
165        let settings: Settings = toml::from_str(toml).unwrap();
166        assert_eq!(settings.log_format, LogFormat::Pretty);
167    }
168
169    #[test]
170    fn test_deserialize_log_format_json() {
171        let toml = r#"
172            log_format = "json"
173        "#;
174
175        let settings: Settings = toml::from_str(toml).unwrap();
176        assert_eq!(settings.log_format, LogFormat::Json);
177    }
178
179    #[test]
180    fn test_deserialize_log_format_invalid() {
181        let toml = r#"
182        log_level = "no_log_format"
183        "#;
184
185        let settings: Result<Settings, toml::de::Error> = toml::from_str(toml);
186        assert!(settings.is_err());
187    }
188}
189
190#[cfg(test)]
191mod log_level_tests {
192    use serde::Deserialize;
193
194    use super::*;
195
196    #[derive(Debug, Deserialize)]
197    struct Settings {
198        log_level: LogLevel,
199    }
200
201    #[test]
202    fn test_deserialize_log_level_trace() {
203        let toml = r#"
204            log_level = "trace"
205        "#;
206
207        let settings: Settings = toml::from_str(toml).unwrap();
208        assert_eq!(settings.log_level, LogLevel::Trace);
209    }
210
211    #[test]
212    fn test_deserialize_log_level_debug() {
213        let toml = r#"
214            log_level = "debug"
215        "#;
216
217        let settings: Settings = toml::from_str(toml).unwrap();
218        assert_eq!(settings.log_level, LogLevel::Debug);
219    }
220
221    #[test]
222    fn test_deserialize_log_level_info() {
223        let toml = r#"
224            log_level = "info"
225        "#;
226
227        let settings: Settings = toml::from_str(toml).unwrap();
228        assert_eq!(settings.log_level, LogLevel::Info);
229    }
230
231    #[test]
232    fn test_deserialize_log_level_warn() {
233        let toml = r#"
234            log_level = "warn"
235        "#;
236
237        let settings: Settings = toml::from_str(toml).unwrap();
238        assert_eq!(settings.log_level, LogLevel::Warn);
239    }
240
241    #[test]
242    fn test_deserialize_log_level_error() {
243        let toml = r#"
244            log_level = "error"
245        "#;
246
247        let settings: Settings = toml::from_str(toml).unwrap();
248        assert_eq!(settings.log_level, LogLevel::Error);
249    }
250
251    #[test]
252    fn test_deserialize_log_level_uppercase() {
253        let toml = r#"
254        log_level = "DEBUG"
255        "#;
256
257        let settings: Settings = toml::from_str(toml).unwrap();
258        assert_eq!(settings.log_level, LogLevel::Debug);
259    }
260
261    #[test]
262    fn test_deserialize_log_level_invalid() {
263        let toml = r#"
264        log_level = "no_log_level"
265        "#;
266
267        let settings: Result<Settings, toml::de::Error> = toml::from_str(toml);
268        assert!(settings.is_err());
269    }
270}
271
272#[cfg(test)]
273mod log_tests {
274    use super::*;
275
276    #[derive(Debug, Deserialize)]
277    struct Settings {
278        pub log: Log,
279    }
280
281    #[test]
282    fn test_deserialize_whole_log() {
283        let toml = r#"
284        [no_log]
285        foo = "bar"
286
287        [log]
288        level = "trace"
289        level_web_server = "debug"
290        format = "compact"
291        "#;
292
293        let settings: Settings = toml::from_str(toml).unwrap();
294        assert_eq!(settings.log.level, LogLevel::Trace);
295        assert_eq!(settings.log.level_web_server, LogLevel::Debug);
296        assert_eq!(settings.log.format, LogFormat::Compact);
297    }
298}