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