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!(
82                            "cannot parse level for default logger: '{}'",
83                            kv_str
84                        ));
85                    }
86                }
87                (Some(logger_name), Some(level), None) => {
88                    if let Some(level) = LevelFilter::from_str_for_env(level) {
89                        (EnvLevelLogger::from_key(logger_name), level)
90                    } else {
91                        return Err(format!(
92                            "cannot parse level for logger '{}': '{}'",
93                            logger_name, kv_str
94                        ));
95                    }
96                }
97                _ => {
98                    return Err(format!("invalid kv: '{}'", kv_str));
99                }
100            };
101
102            match env_level.entry(logger) {
103                Entry::Occupied(_) => {
104                    return Err(format!("specified level multiple times: '{}'", kv_str));
105                }
106                Entry::Vacant(entry) => entry.insert(level),
107            };
108        }
109
110        Ok(env_level)
111    })()
112    .map_err(EnvLevelError::ParseEnvVar)
113}
114
115#[must_use]
116pub(crate) fn logger_level(kind: LoggerKind) -> Option<LevelFilter> {
117    logger_level_inner(ENV_LEVEL.read().unwrap().as_ref()?, kind)
118}
119
120#[derive(Clone, Eq, PartialEq, Debug)]
121pub(crate) enum LoggerKind<'a> {
122    Default,
123    Other(Option<&'a str>),
124}
125
126#[must_use]
127pub(crate) fn logger_level_inner(env_level: &EnvLevel, kind: LoggerKind) -> Option<LevelFilter> {
128    let level = match kind {
129        LoggerKind::Default => env_level.get(&EnvLevelLogger::Default)?,
130        LoggerKind::Other(logger_name) => env_level
131            .get(&EnvLevelLogger::from_logger(logger_name))
132            .or_else(|| env_level.get(&EnvLevelLogger::AllExceptDefault))?,
133    };
134    Some(*level)
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::Level;
141
142    #[test]
143    fn validation() {
144        macro_rules! assert_levels {
145            ($env_level:expr, DEFAULT => $default:expr, UNNAMED => $unnamed:expr, NAMED($name:literal) => $named:expr $(,)?) => {
146                assert_eq!(
147                    logger_level_inner(&$env_level, LoggerKind::Default),
148                    $default
149                );
150                assert_eq!(
151                    logger_level_inner(&$env_level, LoggerKind::Other(None)),
152                    $unnamed
153                );
154                assert_eq!(
155                    logger_level_inner(&$env_level, LoggerKind::Other(Some($name))),
156                    $named
157                );
158            };
159        }
160
161        {
162            let mut env_level = HashMap::new();
163            env_level.insert(
164                EnvLevelLogger::Default,
165                LevelFilter::MoreSevereEqual(Level::Debug),
166            );
167            assert_eq!(from_str_inner("dEBUg").unwrap(), env_level);
168
169            assert_levels!(
170                env_level,
171                DEFAULT => Some(LevelFilter::MoreSevereEqual(Level::Debug)),
172                UNNAMED => None,
173                NAMED("name") => None,
174            );
175        }
176
177        {
178            let mut env_level = HashMap::new();
179            env_level.insert(EnvLevelLogger::Default, LevelFilter::All);
180            env_level.insert(
181                EnvLevelLogger::Unnamed,
182                LevelFilter::MoreSevereEqual(Level::Info),
183            );
184            assert_eq!(from_str_inner("aLl,=inFo").unwrap(), env_level);
185
186            assert_levels!(
187                env_level,
188                DEFAULT => Some(LevelFilter::All),
189                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Info)),
190                NAMED("name") => None,
191            );
192        }
193
194        {
195            let mut env_level = HashMap::new();
196            env_level.insert(EnvLevelLogger::Default, LevelFilter::Off);
197            env_level.insert(
198                EnvLevelLogger::Unnamed,
199                LevelFilter::MoreSevereEqual(Level::Info),
200            );
201            env_level.insert(
202                EnvLevelLogger::AllExceptDefault,
203                LevelFilter::MoreSevereEqual(Level::Error),
204            );
205            assert_eq!(from_str_inner("oFf,=iNfo,*=erRor").unwrap(), env_level);
206
207            assert_levels!(
208                env_level,
209                DEFAULT => Some(LevelFilter::Off),
210                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Info)),
211                NAMED("name") => Some(LevelFilter::MoreSevereEqual(Level::Error)),
212            );
213        }
214
215        {
216            let mut env_level = HashMap::new();
217            env_level.insert(
218                EnvLevelLogger::Unnamed,
219                LevelFilter::MoreSevereEqual(Level::Warn),
220            );
221            env_level.insert(
222                EnvLevelLogger::Named("name".into()),
223                LevelFilter::MoreSevereEqual(Level::Trace),
224            );
225            assert_eq!(from_str_inner("=wArn,name=trAce").unwrap(), env_level);
226
227            assert_levels!(
228                env_level,
229                DEFAULT => None,
230                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Warn)),
231                NAMED("name") => Some(LevelFilter::MoreSevereEqual(Level::Trace)),
232            );
233        }
234
235        {
236            let mut env_level = HashMap::new();
237            env_level.insert(
238                EnvLevelLogger::AllExceptDefault,
239                LevelFilter::MoreSevereEqual(Level::Warn),
240            );
241            env_level.insert(
242                EnvLevelLogger::Named("name".into()),
243                LevelFilter::MoreSevereEqual(Level::Trace),
244            );
245            assert_eq!(from_str_inner("*=wArn,name=trAce").unwrap(), env_level);
246
247            assert_levels!(
248                env_level,
249                DEFAULT => None,
250                UNNAMED => Some(LevelFilter::MoreSevereEqual(Level::Warn)),
251                NAMED("name") => Some(LevelFilter::MoreSevereEqual(Level::Trace)),
252            );
253        }
254
255        {
256            let mut env_level = HashMap::new();
257            env_level.insert(EnvLevelLogger::Default, LevelFilter::All);
258            env_level.insert(EnvLevelLogger::AllExceptDefault, LevelFilter::All);
259            assert_eq!(from_str_inner("all,*=all").unwrap(), env_level);
260
261            assert_levels!(
262                env_level,
263                DEFAULT => Some(LevelFilter::All),
264                UNNAMED => Some(LevelFilter::All),
265                NAMED("name") => Some(LevelFilter::All),
266            );
267        }
268
269        {
270            let mut env_level = HashMap::new();
271            env_level.insert(EnvLevelLogger::Default, LevelFilter::Off);
272            env_level.insert(EnvLevelLogger::AllExceptDefault, LevelFilter::All);
273            assert_eq!(from_str_inner("off,*=all").unwrap(), env_level);
274
275            assert_levels!(
276                env_level,
277                DEFAULT => Some(LevelFilter::Off),
278                UNNAMED => Some(LevelFilter::All),
279                NAMED("name") => Some(LevelFilter::All),
280            );
281        }
282    }
283}