spdlog/
env_level.rs

1use std::{
2    collections::{hash_map::Entry, HashMap},
3    env::VarError,
4};
5
6use thiserror::Error;
7
8use crate::{sync::*, LevelFilter};
9
10pub(crate) type EnvLevel = HashMap<EnvLevelLogger, LevelFilter>;
11
12static ENV_LEVEL: Lazy<RwLock<Option<EnvLevel>>> = Lazy::new(|| RwLock::new(None));
13
14#[derive(Clone, Eq, PartialEq, Hash, Debug)]
15pub(crate) enum EnvLevelLogger {
16    Default,
17    Named(String),
18    Unnamed,
19    AllExceptDefault,
20}
21
22/// The error type of environment level initialization.
23#[derive(Error, Debug)]
24pub enum EnvLevelError {
25    /// Fetch environment variable error.
26    #[error("fetch environment variable error: {0}")]
27    FetchEnvVar(VarError),
28
29    /// Parse environment variable error, usually caused by incorrect format.
30    #[error("parse environment variable error: {0}")]
31    ParseEnvVar(
32        /// Parse error description
33        String,
34    ),
35}
36
37impl EnvLevelLogger {
38    #[must_use]
39    fn from_key(logger_name: &str) -> Self {
40        if logger_name.is_empty() {
41            EnvLevelLogger::Unnamed
42        } else if logger_name == "*" {
43            EnvLevelLogger::AllExceptDefault
44        } else {
45            EnvLevelLogger::Named(logger_name.into())
46        }
47    }
48
49    #[must_use]
50    fn from_logger(logger_name: Option<&str>) -> Self {
51        match logger_name {
52            None => Self::Unnamed,
53            Some(name) => Self::Named(name.into()),
54        }
55    }
56}
57
58pub(crate) fn from_str(var: &str) -> Result<(), EnvLevelError> {
59    let env_level = from_str_inner(var)?;
60    *ENV_LEVEL.write_expect() = Some(env_level);
61    Ok(())
62}
63
64pub(crate) fn from_str_inner(var: &str) -> Result<EnvLevel, EnvLevelError> {
65    (|| {
66        let mut env_level = EnvLevel::new();
67
68        for kv_str in var.split(',').map(str::trim) {
69            if kv_str.is_empty() {
70                continue;
71            }
72
73            let mut kv = kv_str.split('=');
74            let (left, right) = (kv.next().map(str::trim), kv.next().map(str::trim));
75
76            let (logger, level) = match (left, right, kv.next()) {
77                (Some(default_logger_level), None, None) => {
78                    if let Some(level) = LevelFilter::from_str_for_env(default_logger_level) {
79                        (EnvLevelLogger::Default, level)
80                    } else {
81                        return Err(format!("cannot parse level for default logger: '{kv_str}'"));
82                    }
83                }
84                (Some(logger_name), Some(level), None) => {
85                    if let Some(level) = LevelFilter::from_str_for_env(level) {
86                        (EnvLevelLogger::from_key(logger_name), level)
87                    } else {
88                        return Err(format!(
89                            "cannot parse level for logger '{logger_name}': '{kv_str}'"
90                        ));
91                    }
92                }
93                _ => {
94                    return Err(format!("invalid kv: '{kv_str}'"));
95                }
96            };
97
98            match env_level.entry(logger) {
99                Entry::Occupied(_) => {
100                    return Err(format!("specified level multiple times: '{kv_str}'"));
101                }
102                Entry::Vacant(entry) => entry.insert(level),
103            };
104        }
105
106        Ok(env_level)
107    })()
108    .map_err(EnvLevelError::ParseEnvVar)
109}
110
111#[must_use]
112pub(crate) fn logger_level(kind: LoggerKind) -> Option<LevelFilter> {
113    logger_level_inner(ENV_LEVEL.read_expect().as_ref()?, kind)
114}
115
116#[derive(Clone, Eq, PartialEq, Debug)]
117pub(crate) enum LoggerKind<'a> {
118    Default,
119    Other(Option<&'a str>),
120}
121
122#[must_use]
123pub(crate) fn logger_level_inner(env_level: &EnvLevel, kind: LoggerKind) -> Option<LevelFilter> {
124    let level = match kind {
125        LoggerKind::Default => env_level.get(&EnvLevelLogger::Default)?,
126        LoggerKind::Other(logger_name) => env_level
127            .get(&EnvLevelLogger::from_logger(logger_name))
128            .or_else(|| env_level.get(&EnvLevelLogger::AllExceptDefault))?,
129    };
130    Some(*level)
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::Level;
137
138    #[test]
139    fn validation() {
140        macro_rules! assert_levels {
141            ($env_level:expr, DEFAULT => $default:expr, UNNAMED => $unnamed:expr, NAMED($name:literal) => $named:expr $(,)?) => {
142                assert_eq!(
143                    logger_level_inner(&$env_level, LoggerKind::Default),
144                    $default
145                );
146                assert_eq!(
147                    logger_level_inner(&$env_level, LoggerKind::Other(None)),
148                    $unnamed
149                );
150                assert_eq!(
151                    logger_level_inner(&$env_level, LoggerKind::Other(Some($name))),
152                    $named
153                );
154            };
155        }
156
157        {
158            let mut env_level = HashMap::new();
159            env_level.insert(
160                EnvLevelLogger::Default,
161                LevelFilter::MoreSevereEqual(Level::Debug),
162            );
163            assert_eq!(from_str_inner("dEBUg").unwrap(), env_level);
164
165            assert_levels!(
166                env_level,
167                DEFAULT => Some(LevelFilter::MoreSevereEqual(Level::Debug)),
168                UNNAMED => None,
169                NAMED("name") => None,
170            );
171        }
172
173        {
174            let mut env_level = HashMap::new();
175            env_level.insert(EnvLevelLogger::Default, LevelFilter::All);
176            env_level.insert(
177                EnvLevelLogger::Unnamed,
178                LevelFilter::MoreSevereEqual(Level::Info),
179            );
180            assert_eq!(from_str_inner("aLl,=inFo").unwrap(), env_level);
181
182            assert_levels!(
183                env_level,
184                DEFAULT => Some(LevelFilter::All),
185                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Info)),
186                NAMED("name") => None,
187            );
188        }
189
190        {
191            let mut env_level = HashMap::new();
192            env_level.insert(EnvLevelLogger::Default, LevelFilter::Off);
193            env_level.insert(
194                EnvLevelLogger::Unnamed,
195                LevelFilter::MoreSevereEqual(Level::Info),
196            );
197            env_level.insert(
198                EnvLevelLogger::AllExceptDefault,
199                LevelFilter::MoreSevereEqual(Level::Error),
200            );
201            assert_eq!(from_str_inner("oFf,=iNfo,*=erRor").unwrap(), env_level);
202
203            assert_levels!(
204                env_level,
205                DEFAULT => Some(LevelFilter::Off),
206                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Info)),
207                NAMED("name") => Some(LevelFilter::MoreSevereEqual(Level::Error)),
208            );
209        }
210
211        {
212            let mut env_level = HashMap::new();
213            env_level.insert(
214                EnvLevelLogger::Unnamed,
215                LevelFilter::MoreSevereEqual(Level::Warn),
216            );
217            env_level.insert(
218                EnvLevelLogger::Named("name".into()),
219                LevelFilter::MoreSevereEqual(Level::Trace),
220            );
221            assert_eq!(from_str_inner("=wArn,name=trAce").unwrap(), env_level);
222
223            assert_levels!(
224                env_level,
225                DEFAULT => None,
226                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Warn)),
227                NAMED("name") => Some(LevelFilter::MoreSevereEqual(Level::Trace)),
228            );
229        }
230
231        {
232            let mut env_level = HashMap::new();
233            env_level.insert(
234                EnvLevelLogger::AllExceptDefault,
235                LevelFilter::MoreSevereEqual(Level::Warn),
236            );
237            env_level.insert(
238                EnvLevelLogger::Named("name".into()),
239                LevelFilter::MoreSevereEqual(Level::Trace),
240            );
241            assert_eq!(from_str_inner("*=wArn,name=trAce").unwrap(), env_level);
242
243            assert_levels!(
244                env_level,
245                DEFAULT => None,
246                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Warn)),
247                NAMED("name") => Some(LevelFilter::MoreSevereEqual(Level::Trace)),
248            );
249        }
250
251        {
252            let mut env_level = HashMap::new();
253            env_level.insert(EnvLevelLogger::Default, LevelFilter::All);
254            env_level.insert(EnvLevelLogger::AllExceptDefault, LevelFilter::All);
255            assert_eq!(from_str_inner("all,*=all").unwrap(), env_level);
256
257            assert_levels!(
258                env_level,
259                DEFAULT => Some(LevelFilter::All),
260                UNNAMED => Some(LevelFilter::All),
261                NAMED("name") => Some(LevelFilter::All),
262            );
263        }
264
265        {
266            let mut env_level = HashMap::new();
267            env_level.insert(EnvLevelLogger::Default, LevelFilter::Off);
268            env_level.insert(EnvLevelLogger::AllExceptDefault, LevelFilter::All);
269            assert_eq!(from_str_inner("off,*=all").unwrap(), env_level);
270
271            assert_levels!(
272                env_level,
273                DEFAULT => Some(LevelFilter::Off),
274                UNNAMED => Some(LevelFilter::All),
275                NAMED("name") => Some(LevelFilter::All),
276            );
277        }
278    }
279}