Skip to main content

netter_logger/
lib.rs

1use chrono::Local;
2use colored::*;
3use log::{Level, LevelFilter};
4use std::path::{Path, PathBuf};
5use std::io::ErrorKind;
6
7const LOGS_PREFIX: &str = "netter_log";
8const SEPARATOR: &str = "_";
9const TIMESTAMP_FORMAT: &str = "%Y-%m-%d_%H-%M-%S";
10const LOG_EXTENSION: &str = "log";
11const CONSOLE_TIMESTAMP_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.3f";
12
13fn generate_filename_only() -> String {
14    let now = Local::now();
15    let timestamp = now.format(TIMESTAMP_FORMAT).to_string();
16    format!("{}{}{}.{}", LOGS_PREFIX, SEPARATOR, timestamp, LOG_EXTENSION)
17}
18
19fn ensure_log_directory_exists(log_dir: &Path) -> std::io::Result<()> {
20    if !log_dir.exists() {
21        std::fs::create_dir_all(log_dir)?;
22    }
23    Ok(())
24}
25
26pub fn init(
27    log_dir: Option<impl AsRef<Path>>,
28    console_level: LevelFilter,
29    file_level: LevelFilter,
30) -> Result<(), fern::InitError> {
31
32    let mut log_file_path: Option<PathBuf> = None;
33
34    if let Some(dir) = log_dir {
35        let dir_path = dir.as_ref();
36        if let Err(e) = ensure_log_directory_exists(dir_path) {
37             eprintln!("CRITICAL: Не удалось создать директорию для логов '{}': {}", dir_path.display(), e);
38             return Err(fern::InitError::Io(std::io::Error::new(
39                ErrorKind::Other,
40                format!("Не удалось создать директорию для логов '{}': {}", dir_path.display(), e),
41            )));
42        }
43        log_file_path = Some(dir_path.join(generate_filename_only()));
44    }
45
46    let console_dispatch = fern::Dispatch::new()
47        .format(|out, message, record| {
48            let level_str = match record.level() {
49                Level::Error => "ERROR".red().bold(),
50                Level::Warn => "WARN ".yellow().bold(),
51                Level::Info => "INFO ".green().bold(),
52                Level::Debug => "DEBUG".blue().bold(),
53                Level::Trace => "TRACE".magenta().bold(),
54            };
55
56            let timestamp = Local::now().format(CONSOLE_TIMESTAMP_FORMAT).to_string();
57
58            let thread_name = std::thread::current().name().unwrap_or("unnamed").to_string();
59            let target = record.target();
60
61            out.finish(format_args!(
62                "[{}] [{}] [{}] [{}] {}",
63                timestamp,
64                level_str,
65                thread_name,
66                target,
67                message
68            ))
69        })
70        .level(console_level)
71        .chain(std::io::stdout());
72
73    let mut base_dispatch = fern::Dispatch::new()
74        .level(LevelFilter::Trace)
75        .chain(console_dispatch);
76
77    if let Some(path) = &log_file_path {
78        let file_dispatch = fern::Dispatch::new()
79            .format(|out, message, record| {
80                let timestamp = Local::now().format(CONSOLE_TIMESTAMP_FORMAT).to_string();
81                 let thread_name = std::thread::current().name().unwrap_or("unnamed").to_string();
82                 let target = record.target();
83                out.finish(format_args!(
84                    "[{}] [{:<5}] [{}] [{}] [{}:{}] {}",
85                    timestamp,
86                    record.level(),
87                    thread_name,
88                    target,
89                    record.file().unwrap_or("?"),
90                    record.line().unwrap_or(0),
91                    message
92                ))
93            })
94            .level(file_level)
95            .chain(fern::log_file(path)?);
96
97        base_dispatch = base_dispatch.chain(file_dispatch);
98    }
99
100    base_dispatch.apply()?;
101
102    log::info!("Логгер инициализирован. Уровень консоли: {}, Уровень файла: {}", console_level, file_level);
103    if let Some(path) = &log_file_path {
104         log::info!("Запись логов в файл: {}", path.display());
105    } else {
106         log::info!("Запись логов в файл отключена.");
107    }
108
109    Ok(())
110}