captains_log/
env.rs

1/// ## Configure by environment
2///
3/// There is a recipe [env_logger()](crate::recipe::env_logger()) to configure a file logger or
4/// console logger from env. As simple as:
5///
6/// ``` rust
7/// use captains_log::recipe;
8/// recipe::env_logger("LOG_FILE", "LOG_LEVEL").build().expect("setup log");
9/// ```
10///
11/// If you want to custom more, setup your config with [env_or] helper.
12use log::Level;
13use std::path::{Path, PathBuf};
14use std::str::FromStr;
15
16#[doc(hidden)]
17#[macro_export(local_inner_macros)]
18macro_rules! impl_from_env {
19    ($type: tt) => {
20        impl<'a> Into<$type> for EnvVarDefault<'a, $type> {
21            #[inline]
22            fn into(self) -> $type {
23                if let Ok(v) = std::env::var(&self.name) {
24                    match $type::from_str(&v) {
25                        Ok(r) => return r,
26                        Err(_) => {
27                            std::eprintln!(
28                                "env {}={} is not valid, set to {:?}",
29                                self.name,
30                                v,
31                                self.default
32                            );
33                        }
34                    }
35                }
36                return self.default;
37            }
38        }
39    };
40}
41
42/// An intermedium type for [env_or()], parsing environment with default values.
43pub struct EnvVarDefault<'a, T> {
44    pub(crate) name: &'a str,
45    pub(crate) default: T,
46}
47
48/// To config some logger setting with env.
49///
50/// Read value from environment, and set with default if not exists.
51///
52/// NOTE: the arguments to load from env_or() must support owned values.
53///
54/// Example:
55///
56/// ```rust
57/// use captains_log::{*, env::env_or};
58/// let _level: log::Level = env_or("LOG_LEVEL", Level::Info).into();
59/// let _file_path: String = env_or("LOG_FILE", "/tmp/test.log").into();
60/// let _console: ConsoleTarget = env_or("LOG_CONSOLE", ConsoleTarget::Stdout).into();
61/// ```
62pub fn env_or<'a, T>(name: &'a str, default: T) -> EnvVarDefault<'a, T> {
63    EnvVarDefault { name, default }
64}
65
66impl<'a> Into<String> for EnvVarDefault<'a, &'a str> {
67    fn into(self) -> String {
68        if let Ok(v) = std::env::var(&self.name) {
69            return v;
70        }
71        return self.default.to_string();
72    }
73}
74
75impl<'a, P: AsRef<Path>> Into<PathBuf> for EnvVarDefault<'a, P> {
76    fn into(self) -> PathBuf {
77        if let Some(v) = std::env::var_os(&self.name) {
78            if v.len() > 0 {
79                return PathBuf::from(v);
80            }
81        }
82        return self.default.as_ref().to_path_buf();
83    }
84}
85
86crate::impl_from_env!(Level);
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::recipe;
92    use crate::*;
93
94    #[test]
95    fn test_env_config() {
96        // test log level
97        unsafe { std::env::set_var("LEVEL", "warn") };
98        let level: Level = env_or("LEVEL", Level::Debug).into();
99        assert_eq!(level, Level::Warn);
100        unsafe { std::env::set_var("LEVEL", "WARN") };
101        let level: Level = env_or("LEVEL", Level::Debug).into();
102        assert_eq!(level, Level::Warn);
103
104        assert_eq!(ConsoleTarget::from_str("Stdout").unwrap(), ConsoleTarget::Stdout);
105        assert_eq!(ConsoleTarget::from_str("StdERR").unwrap(), ConsoleTarget::Stderr);
106        assert_eq!(ConsoleTarget::from_str("1").unwrap(), ConsoleTarget::Stdout);
107        assert_eq!(ConsoleTarget::from_str("2").unwrap(), ConsoleTarget::Stderr);
108        assert_eq!(ConsoleTarget::from_str("0").unwrap_err(), ());
109
110        // test console target
111        unsafe { std::env::set_var("CONSOLE", "stderr") };
112        let target: ConsoleTarget = env_or("CONSOLE", ConsoleTarget::Stdout).into();
113        assert_eq!(target, ConsoleTarget::Stderr);
114        unsafe { std::env::set_var("CONSOLE", "") };
115        let target: ConsoleTarget = env_or("CONSOLE", ConsoleTarget::Stdout).into();
116        assert_eq!(target, ConsoleTarget::Stdout);
117
118        // test path
119        unsafe { std::env::set_var("LOG_PATH", "/tmp/test.log") };
120        let path: PathBuf = env_or("LOG_PATH", "/tmp/other.log").into();
121        assert_eq!(path, Path::new("/tmp/test.log").to_path_buf());
122
123        unsafe { std::env::set_var("LOG_PATH", "") };
124        let path: PathBuf = env_or("LOG_PATH", "/tmp/other.log").into();
125        assert_eq!(path, Path::new("/tmp/other.log").to_path_buf());
126
127        let _builder = recipe::raw_file_logger(env_or("LOG_PATH", "/tmp/other.log"), Level::Info);
128        let _builder =
129            recipe::raw_file_logger(env_or("LOG_PATH", "/tmp/other.log".to_string()), Level::Info);
130    }
131}