loggit/
logger.rs

1//! The logger module provides configuration functions and macros for logging.
2//!
3//! You can change the log level, formatting, and enable/disable colorized output.
4//!
5//! The public macros (`trace!`, `debug!`, `info!`, `warn!`, `error!`) use the internal
6//! handlers to format and print the log message.
7
8use file_handler::file_manager::FileManager;
9use formatter::{LogColor, LogFormatter};
10use std::sync::RwLockWriteGuard;
11
12use crate::{
13    helper::{get_current_date_in_string, get_current_time_in_string},
14    Config, Level, CONFIG,
15};
16//pub(crate) mod formatter;
17pub mod file_handler;
18pub mod formatter;
19pub mod from_file_config;
20
21struct LogInfo {
22    file: String,
23    line: u32,
24    message: String,
25    level: Level,
26}
27
28// -- Getter functions for config --
29fn get_log_level() -> Level {
30    get_config().level
31}
32fn get_config() -> Config {
33    let config_lock = match CONFIG.read() {
34        Ok(r) => r,
35        Err(e) => {
36            eprintln!("Problem with getting config, here's an error: {}", e);
37            return Default::default();
38        }
39    };
40    if let Some(ref cfg) = *config_lock {
41        cfg.clone()
42    } else {
43        eprintln!("Problem with getting config!");
44        Default::default()
45    }
46}
47
48fn get_log_format(level: Level) -> LogFormatter {
49    let tmp_cfg = get_config();
50    match level {
51        Level::TRACE => tmp_cfg.trace_log_format,
52        Level::DEBUG => tmp_cfg.debug_log_format,
53        Level::INFO => tmp_cfg.info_log_format,
54        Level::WARN => tmp_cfg.warn_log_format,
55        Level::ERROR => tmp_cfg.error_log_format,
56    }
57}
58
59fn get_file_manager() -> Option<FileManager> {
60    let tmp_cfg = get_config();
61    tmp_cfg.file_manager
62}
63
64fn get_write_config() -> Option<RwLockWriteGuard<'static, Option<Config>>> {
65    match CONFIG.write() {
66        Ok(guard) => Some(guard),
67        Err(e) => {
68            eprintln!(
69                "An error while getting the config to write, here's an error: {}",
70                e
71            );
72            None
73        } // Handle error case safely
74    }
75}
76
77// -- Public configuration setter functions --
78//
79
80/// Makes all logs to be saved to files. The files take names accordinlgy to the provided format.
81///
82///  Configures Loggit to write logs to a file. The `format` string is used to generate the file name and must include a file extension. The format may include placeholders such as:
83///  - `{time}` – Current time.
84///  - `{date}` – Current date.
85///  - `{level}` - Current loggin level.
86///  - Other literal text.
87///
88///- **Allowed values:**  
89///  - The format string **must** end with a text section containing a file extension (e.g. `.txt` or `.log`).  
90///  - Any forbidden characters such as `<`, `>`, `&`, or `%` will cause configuration to fail.  
91///  - *Examples:*  
92///    - `"app_{date}_{time}.txt"`  
93///    - `"{level}-log-on-{date}.log"`
94pub fn set_file(format: &str) {
95    let file_manager = match FileManager::init_from_string(format, get_config()) {
96        Ok(r) => r,
97        Err(e) => {
98            eprintln!(
99                "Couldn't establish file config due to the next reason: {}",
100                e
101            );
102            return;
103        }
104    };
105
106    let config_lock = get_write_config();
107    if config_lock.is_none() {
108        eprintln!("An error while getting the config to write!");
109        return;
110    }
111    let mut config_lock = config_lock.unwrap();
112    if let Some(ref mut cfg) = *config_lock {
113        cfg.file_manager = Some(file_manager);
114    }
115}
116
117///Enables file compression for log archival.
118///
119///- **Description:**  
120///  Sets the compression type for log files. After file logging is configured, you can enable compression to archive old logs.
121///
122///- **Allowed values:**  
123///  - Accepts only a single allowed value: `"zip"`.  
124///  - Any other string will output an error and leave the compression configuration unchanged.
125pub fn set_compression(ctype: &str) {
126    let f_manager = get_file_manager();
127    if f_manager.is_none() {
128        eprintln!("Can't set a compression when the file isn't set!");
129        return;
130    }
131    let mut f_manager = f_manager.unwrap();
132    f_manager.set_compression(ctype);
133
134    let config_lock = get_write_config();
135    if config_lock.is_none() {
136        eprintln!("An error while getting the config to write!");
137        return;
138    }
139    let mut config_lock = config_lock.unwrap();
140    if let Some(ref mut cfg) = *config_lock {
141        cfg.file_manager = Some(f_manager);
142    }
143}
144
145///Adds a new constraint for rotating log files.
146///
147///- **Description:**  
148///  Adds a rotation strategy so that log files are rotated based on either time or file size. When a log file “expires” under the configured constraint, a new file is automatically created (and optionally compressed).
149///
150///- **Allowed values:**  
151///  The `constraint` string can be in one of the following formats:
152///  - **Period rotation:**  
153///    - Numeric value followed by a unit:  
154///      - `"1 hour"`, `"2 day"`, `"33 week"`, `"6 month"`, `"12 year"`  
155///      - The unit is case sensitive and must match exactly (e.g. `" hour"`, `" day"`, etc.).
156///  - **Time-based rotation:**  
157///    - Time in a 24‑hour format using a colon separator:  
158///      - `"HH:MM"` (e.g. `"12:30"`).
159///  - **Size-based rotation:**  
160///    - Numeric value followed by a size unit:  
161///      - `"500 KB"`, `"5 MB"`, `"1 GB"`, or `"2 TB"`  
162///      - Note the space before the unit.
163///
164///- If an incorrect value is provided, the rotation is not added and an error message is logged.
165pub fn add_rotation(constraint: &str) {
166    let f_manager = get_file_manager();
167    if f_manager.is_none() {
168        eprintln!("Can't set a compression when the file isn't set!");
169        return;
170    }
171    let mut f_manager = f_manager.unwrap();
172
173    if !f_manager.add_rotation(constraint) {
174        eprintln!("Incorrect value given for the rotation!");
175        return;
176    }
177
178    let config_lock = get_write_config();
179    if config_lock.is_none() {
180        eprintln!("An error while getting the config to write!");
181        return;
182    }
183    let mut config_lock = config_lock.unwrap();
184    if let Some(ref mut cfg) = *config_lock {
185        cfg.file_manager = Some(f_manager);
186    }
187}
188
189/// Sets the minimum log level to display.
190/// Messages with a level lower than the given level will be ignored.
191pub fn set_log_level(lvl: Level) {
192    let config_lock = get_write_config();
193    if config_lock.is_none() {
194        eprintln!("An error while getting the config to write!");
195        return;
196    }
197    let mut config_lock = config_lock.unwrap();
198    if let Some(ref mut cfg) = *config_lock {
199        cfg.level = lvl;
200    }
201}
202/// Enables or disables terminal output of log messages.
203/// When set to false, log messages will not be printed to the terminal.
204pub fn set_print_to_terminal(val: bool) {
205    let config_lock = get_write_config();
206    if config_lock.is_none() {
207        eprintln!("An error while getting the config to write!");
208        return;
209    }
210    let mut config_lock = config_lock.unwrap();
211    if let Some(ref mut cfg) = *config_lock {
212        cfg.print_to_terminal = val;
213    }
214}
215/// Enables or disables colorized output of log messages.
216/// If enabled, logs will be printed with colors as configured in the format.
217pub fn set_colorized(val: bool) {
218    let config_lock = get_write_config();
219    if config_lock.is_none() {
220        eprintln!("An error while getting the config to write!");
221        return;
222    }
223    let mut config_lock = config_lock.unwrap();
224    if let Some(ref mut cfg) = *config_lock {
225        cfg.colorized = val;
226    }
227}
228
229/// Sets a global log formatting string for all log levels.
230/// This function updates the formatting of each level to the given template.
231pub fn set_global_formatting(format: &str) {
232    set_level_formatting(Level::TRACE, format);
233    set_level_formatting(Level::DEBUG, format);
234    set_level_formatting(Level::INFO, format);
235    set_level_formatting(Level::WARN, format);
236    set_level_formatting(Level::ERROR, format);
237}
238
239/// Sets a custom log formatting string for the specified log level.
240///
241/// The formatting string may contain placeholders like `{level}`, `{file}`, `{line}`, and `{message}`.
242pub fn set_level_formatting(level: Level, format: &str) {
243    let config_lock = get_write_config();
244    if config_lock.is_none() {
245        eprintln!("An error while getting the config to write!");
246        return;
247    }
248    let mut config_lock = config_lock.unwrap();
249    if let Some(ref mut cfg) = *config_lock {
250        match level {
251            Level::TRACE => cfg.trace_log_format = LogFormatter::parse_from_string(format),
252            Level::DEBUG => cfg.debug_log_format = LogFormatter::parse_from_string(format),
253            Level::INFO => cfg.info_log_format = LogFormatter::parse_from_string(format),
254            Level::WARN => cfg.warn_log_format = LogFormatter::parse_from_string(format),
255            Level::ERROR => cfg.error_log_format = LogFormatter::parse_from_string(format),
256        }
257    }
258}
259
260// -- Internal functions for logging --
261fn string_log(log_info: &LogInfo, colorize: bool) -> String {
262    let mut mess_to_print = String::new();
263    let curr_time: String = get_current_time_in_string();
264    let curr_date = get_current_date_in_string();
265    for log_part in get_log_format(log_info.level).parts {
266        let str_to_push = match log_part.part {
267            formatter::LogPart::Message => &log_info.message,
268            formatter::LogPart::Time => &curr_time,
269            formatter::LogPart::File => &log_info.file,
270            formatter::LogPart::Line => &log_info.line.to_string(),
271            formatter::LogPart::Date => &curr_date,
272            formatter::LogPart::Level => &log_info.level.to_string(),
273            formatter::LogPart::Text(text) => &text.clone(),
274        };
275        if colorize && log_part.color.is_some() {
276            let colored_str = LogColor::colorize_str(str_to_push, log_part.color.unwrap());
277            mess_to_print.push_str(&colored_str);
278        } else {
279            mess_to_print.push_str(str_to_push);
280        }
281    }
282    mess_to_print
283}
284fn print_log(log_info: &LogInfo) {
285    let mess_to_print = string_log(log_info, get_config().colorized);
286    println!("{}", mess_to_print);
287}
288fn write_file_log(log_info: &LogInfo) {
289    let mut file_manager = get_file_manager().unwrap();
290    let mess_to_print = string_log(log_info, false);
291
292    let res = file_manager.write_log(&mess_to_print, get_config());
293    match res {
294        Ok(_) => {}
295        Err(e) => {
296            eprintln!(
297                "Couldn't write a log to the file due to the next error: {}",
298                e
299            );
300        }
301    }
302
303    // file manager can be updated, (for example change of file) thus it needs to be updated in the
304    // config
305    let w_conf = get_write_config();
306    if let Some(mut temp_cfg) = w_conf {
307        if let Some(ref mut cfg) = *temp_cfg {
308            cfg.file_manager = Some(file_manager)
309        }
310    }
311}
312fn log_handler(log_info: LogInfo) {
313    if get_config().print_to_terminal {
314        print_log(&log_info);
315    }
316    if get_config().file_manager.is_some() {
317        write_file_log(&log_info);
318    }
319}
320
321// handles call from macro and passes deeper
322fn macro_handler(file: &str, line: u32, deb_str: String, level: Level) {
323    let log_info = LogInfo {
324        file: file.to_string(),
325        line,
326        message: deb_str,
327        level,
328    };
329    if level >= get_log_level() {
330        log_handler(log_info);
331    }
332}
333
334/// Internal function for handling log macros.
335///
336/// It is used by the public logger macros to format and output the log message.
337pub fn __debug_handler(file: &str, line: u32, deb_str: String, level: Level) {
338    macro_handler(file, line, deb_str, level);
339}
340
341// -- Publicly exported logging macros --
342
343#[macro_export]
344/// Logs a message at the TRACE level.
345/// The message is formatted using standard Rust formatting.
346///
347/// # Example
348/// ```rust
349/// use loggit::trace;
350///
351/// trace!("Trace message: {}", "details");
352/// ```
353macro_rules! trace {
354        ($($arg:tt)*) => {{
355            let res_str = format!($($arg)*);
356            $crate::logger::__debug_handler(file!(), line!(), res_str, $crate::Level::TRACE);
357        }};
358    }
359
360#[macro_export]
361/// Logs a message at the DEBUG level.
362/// The message is formatted using standard Rust formatting.
363///
364/// # Example
365/// ```rust
366/// use loggit::debug;
367///
368/// debug!("Debug message: value = {}", 123);
369/// ```
370macro_rules! debug {
371        ($($arg:tt)*) => {{
372            let res_str = format!($($arg)*);
373            $crate::logger::__debug_handler(file!(), line!(), res_str, $crate::Level::DEBUG);
374        }};
375    }
376
377#[macro_export]
378/// Logs a message at the INFO level.
379/// The message is formatted using standard Rust formatting.
380///
381/// # Example
382/// ```rust
383/// use loggit::info;
384///
385/// info!("Informational message.");
386/// ```
387macro_rules! info {
388        ($($arg:tt)*) => {{
389            let res_str = format!($($arg)*);
390            $crate::logger::__debug_handler(file!(), line!(), res_str, $crate::Level::INFO);
391        }};
392    }
393
394#[macro_export]
395/// Logs a message at the WARN level.
396/// The message is formatted using standard Rust formatting.
397///
398/// # Example
399/// ```rust
400/// use loggit::warn;
401///
402/// warn!("Warning: check configuration!");
403/// ```
404macro_rules! warn {
405        ($($arg:tt)*) => {{
406            let res_str = format!($($arg)*);
407            $crate::logger::__debug_handler(file!(), line!(), res_str, $crate::Level::WARN);
408        }};
409    }
410
411#[macro_export]
412/// Logs a message at the ERROR level.
413/// The message is formatted using standard Rust formatting.
414///
415/// # Example
416/// ```rust
417/// use loggit::error;
418///
419/// error!("Error occurred: {}", "example error");
420/// ```
421macro_rules! error {
422        ($($arg:tt)*) => {{
423            let res_str = format!($($arg)*);
424            $crate::logger::__debug_handler(file!(), line!(), res_str, $crate::Level::ERROR);
425        }};
426    }
427
428/// Initializes the logger with default configuration settings.
429pub fn init() {
430    let mut config = CONFIG.write().unwrap();
431    *config = Some(Config {
432        ..Default::default()
433    })
434}