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