Skip to main content

init_log4rs/
init_log4rs.rs

1use std::env;
2use std::fs;
3use std::fs::File;
4use std::io::prelude::*;
5use std::path::PathBuf;
6
7use log4rs;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum InitLog4rsError {
12    #[error(transparent)]
13    Anyhow(#[from] anyhow::Error),
14    #[error(transparent)]
15    IoError(#[from] std::io::Error),
16
17    #[error("{0}")]
18    Other(String),
19}
20
21/// Try to load configuration file from following directories or create new.
22///
23/// * executable dir
24/// * current_dir
25/// * homedir
26/// * temp dir
27pub fn init_log4rs(log_cfg_yaml: &str, log_stem: &str) -> Result<(), InitLog4rsError> {
28    let mut log_path: Vec<PathBuf> = Vec::new();
29
30    // First, check RUST_LOG_DIR environment variable
31    if let Ok(log_dir) = env::var("RUST_LOG_DIR") {
32        log_path.push(PathBuf::from(log_dir));
33    }
34
35    if let Ok(exe) = std::env::current_exe()
36        && let Some(p) = exe.parent()
37    {
38        let mut exe_log_dir = p.to_path_buf();
39        exe_log_dir.push("logs");
40        log_path.push(exe_log_dir);
41    }
42    if let Ok(mut pwd) = env::current_dir() {
43        pwd.push("logs");
44        log_path.push(pwd);
45    }
46    if let Ok(pwd) = env::current_dir()
47        && let Some(parent) = pwd.parent()
48    {
49        let mut path = parent.to_path_buf();
50        path.push("logs");
51        log_path.push(path);
52    }
53    if let Some(mut path) = dirs::home_dir() {
54        path.push("logs");
55        log_path.push(path);
56    }
57    let mut temp_log_dir = env::temp_dir();
58    temp_log_dir.push("logs");
59    log_path.push(temp_log_dir);
60
61    // Search log4rs.yaml first
62    for path in &log_path {
63        let mut log_cfg_file = path.clone();
64        log_cfg_file.push(log_cfg_yaml);
65        match log4rs::init_file(log_cfg_file.clone(), Default::default()) {
66            Ok(_) => {
67                eprintln!("Log dir {}", path.display());
68                return Ok(());
69            }
70            Err(_err) => {
71                // eprintln!("Failed to load {}: {}", path.display(), _err);
72            }
73        }
74    }
75
76    // Create if not found
77    for path in log_path.iter() {
78        match logger_yaml_file_create(path, log_cfg_yaml, log_stem) {
79            Ok(_) => {
80                eprintln!("Log dir {}", path.display());
81                return Ok(());
82            }
83            Err(_err) => {
84                // eprintln!("Failed to load {}: {}", path.display(), _err);
85            }
86        }
87    }
88    return Err(InitLog4rsError::Other(
89        "Failed to initialize log4rs.".to_string(),
90    ));
91}
92
93/// create log4rs configuration file
94fn logger_yaml_file_create(
95    path: &PathBuf,
96    log_cfg: &str,
97    log_stem: &str,
98) -> Result<(), InitLog4rsError> {
99    fs::create_dir_all(path)?;
100
101    let mut log_store = path.clone();
102    log_store.push(log_stem);
103    log_store.set_extension("log");
104    let log_store_file = log_store.to_str().ok_or(InitLog4rsError::Other(
105        "log store path construct fail".to_string(),
106    ))?;
107
108    let mut log_tar = path.clone();
109    log_tar.push(log_stem);
110    log_tar.set_extension("log.{}");
111
112    let log_tar_file = log_tar.to_str().ok_or(InitLog4rsError::Other(
113        "log tar path construct fail".to_string(),
114    ))?;
115
116    let mut log_cfg_file = PathBuf::from(path);
117    log_cfg_file.push(log_cfg);
118
119    let config_str = format!(
120        "
121# Scan this file for changes every 30 seconds
122refresh_rate: 60 seconds
123
124appenders:
125    # stdout
126    stdout:
127        kind: console
128
129    # stderr
130    stderr:
131        kind: console
132        target: stderr
133
134    # rolling_file
135    rolling_file:
136        kind: rolling_file
137        path: {}
138        append: true
139        encoder:
140            # https://docs.rs/log4rs/1.0.0/log4rs/encode/pattern/index.html#formatters
141            pattern: '{{d}} {{l}} {{M}}:{{L}} - {{m}}{{n}}' # Date Level [Module]:[line] - msg \\n
142        policy:
143            kind: compound
144            trigger:
145                kind: size
146                limit: 10 mb # file size limit 102400 Byte
147            roller:
148                kind: fixed_window
149                pattern: {}
150                base: 1
151                count: 20
152
153# loggers:
154#     map_app::my_mod: # custom logger
155#         level: debug
156#         appenders:
157#             - stderr
158#         additive: false
159
160# Set the default logging level to 'info' and attach the 'rolling_file' appender to the root
161root:
162    level: info
163    appenders:
164        - rolling_file
165",
166        log_store_file, log_tar_file
167    );
168
169    // This function will create a file if it does not exist, and will truncate it if it does.
170    let mut output = File::create(log_cfg_file.clone())?;
171    write!(output, "{}", config_str)?;
172    drop(output);
173    log4rs::init_file(log_cfg_file, Default::default())?;
174    Ok(())
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_log4rs() -> anyhow::Result<()> {
183        init_log4rs("log4rs.yaml", "app_log")?;
184        Ok(())
185    }
186}