se_logger/
lib.rs

1//! # se-logger
2//! Simple customizable logging crate.
3//! 
4//! # Getting started
5//! ```
6//! use se_logger::*;
7//! 
8//! fn main() {
9//!     // Initialize the logger with the log file path and log level
10//!     // File path will be test_19_35_33.log (for example)
11//!     log_init("test_%H_%M_%S.log", INFO);
12//!     
13//!     // Now you can use the library anywhere
14//!     info("This is an info message");
15//!     error("This is an error message");
16//! }
17//! ```
18//! Output:
19//! ```
20//! [19:35:33] [INFO] [main] This is an info message
21//! [19:35:33] [ERROR] [main] This is an error message
22//! ```
23
24use std::io::Write;
25
26pub const TRACE: u32 = 5;
27pub const DEBUG: u32 = 4;
28pub const INFO: u32 = 3;
29pub const WARNING: u32 = 2;
30pub const ERROR: u32 = 1;
31pub const FATAL: u32 = 0;
32
33const LOG_PATH_VAR: &str = "SE_LOG_PATH";
34const LOG_LEVEL_VAR: &str = "SE_LOG_LEVEL";
35
36/// Initialize the logger with settings
37/// ### Arguments
38///
39/// - `path` - Path to save log files to. Can be formated according to:
40/// 
41/// <https://docs.rs/chrono/latest/chrono/format/strftime/index.html#specifiers>
42///
43/// #### Example
44/// `log_%F_%H-%M-%S.log` expands to `log_2022-09-02_06-27-44.log`
45///
46/// - `level` - Log level:
47///     - `TRACE` - 5
48///     - `DEBUG` - 4
49///     - `INFO` - 3
50///     - `WARRNING` - 2
51///     - `ERROR` - 1
52///     - `FATAL` - 0
53/// 
54/// ### Notes
55/// `%D`, `%x`, `%R`, `%T`, `%X`, `%r`, `%+` should not
56/// be used as they contain `/` or `:` which are disallowed in filenames.
57/// 
58/// If a path is invalid, the default will be used: `unnamed.log`
59pub fn log_init(path: &str, level: u32) {
60    set_log_path(&current_time_fmt(path));
61    set_log_level(level);
62}
63
64/// Log a generic message
65pub fn log(message: &str) {
66    println!("{message}");
67    let mut f = match std::fs::OpenOptions::new()
68        .append(true)
69        .create(true)
70        .open(get_log_path())
71    {
72        Ok(f) => f,
73        Err(e) => {
74            println!("Logger: Failed to open file: {e}");
75            return;
76        }
77    };
78    match f.write((message.to_string() + "\n").as_bytes()) {
79        Ok(_) => {}
80        Err(e) => {
81            println!("Logger: Failed to write to file: {e}");
82            return;
83        }
84    }
85}
86
87/// Log a trace message
88pub fn trace(message: &str) {
89    log_with_level(message, TRACE);
90}
91/// Log a debug message
92pub fn debug(message: &str) {
93    log_with_level(message, DEBUG);
94}
95/// Log an info message
96pub fn info(message: &str) {
97    log_with_level(message, INFO);
98}
99/// Log a warning message
100pub fn warning(message: &str) {
101    log_with_level(message, WARNING);
102}
103/// Log an error message
104pub fn error(message: &str) {
105    log_with_level(message, ERROR);
106}
107/// Log a fatal message
108pub fn fatal(message: &str) {
109    log_with_level(message, FATAL);
110}
111
112fn log_with_level(message: &str, level: u32) {
113    if get_log_level() >= level {
114        log(&format!(
115            "[{}] [{}] [{}] {}",
116            current_time_fmt("%T"),
117            level_to_string(level),
118            match std::thread::current().name() {
119                Some(s) => s,
120                None => "unnamed thread",
121            },
122            message
123        ))
124    }
125}
126
127fn get_log_level() -> u32 {
128    match std::env::var(LOG_LEVEL_VAR) {
129        Ok(s) => match s.parse::<u32>() {
130            Ok(v) => v,
131            Err(_) => INFO,
132        },
133        Err(_) => INFO,
134    }
135}
136fn set_log_level(level: u32) {
137    if level >= FATAL && level <= TRACE {
138        std::env::set_var(LOG_LEVEL_VAR, level.to_string());
139    }
140}
141fn get_log_path() -> String {
142    match std::env::var(LOG_PATH_VAR) {
143        Ok(s) => s,
144        Err(_) => "unnamed.log".to_string(),
145    }
146}
147fn set_log_path(path: &str) {
148    std::env::set_var(LOG_PATH_VAR, path);
149}
150
151fn level_to_string(level: u32) -> String {
152    match level {
153        TRACE => "TRACE",
154        DEBUG => "DEBUG",
155        INFO => "INFO",
156        WARNING => "WARNING",
157        ERROR => "ERROR",
158        FATAL => "FATAL",
159        _ => "",
160    }
161    .to_string()
162}
163fn current_time_fmt(fmt: &str) -> String {
164    chrono::Local::now().format(fmt).to_string()
165}