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}