Skip to main content

spdlog/
env_level.rs

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