1use crate::utils::{AsyncLogType, get_msg_from_cache, put_msg_to_cache, level_color};
7use crate::quickwit::{QuickwitClient, QuickwitLogEntry};
8use std::collections::{HashMap, VecDeque};
9use std::fs::File;
10use std::io::{LineWriter, Stdout, Write};
11use std::sync::{Mutex, RwLock, mpsc::Sender};
12
13pub trait CustomFilter: Send + Sync + 'static {
15 fn enabled(&self, record: &log::Record) -> bool;
16}
17
18pub struct LogData {
20 pub log_size: u32,
21 pub console: Option<LineWriter<Stdout>>,
22 pub fileout: Option<LineWriter<File>>,
23 pub sender: Option<Sender<AsyncLogType>>,
24 pub plugin: Option<Box<dyn std::io::Write + Send + Sync + 'static>>,
25}
26
27pub struct LogPlus {
29 pub level: log::LevelFilter,
30 pub log_file: String,
31 pub max_size: u32,
32 pub level_filter: RwLock<HashMap<String, log::LevelFilter>>,
33 pub fmt_cache: Mutex<VecDeque<Vec<u8>>>,
34 pub filter: Option<Box<dyn CustomFilter>>,
35 pub logger_data: Mutex<LogData>,
36 pub show_process_id: bool,
37 pub show_thread_info: bool,
38 pub show_module_path: bool,
39 pub highlight_keywords: Vec<String>,
40 pub quickwit_client: Option<QuickwitClient>,
41}
42
43impl LogPlus {
44 pub fn new(
46 level: log::LevelFilter,
47 log_file: String,
48 max_size: u32,
49 filter: Option<Box<dyn CustomFilter>>,
50 logger_data: LogData,
51 show_process_id: bool,
52 show_thread_info: bool,
53 show_module_path: bool,
54 highlight_keywords: Vec<String>,
55 quickwit_client: Option<QuickwitClient>,
56 ) -> Self {
57 Self {
58 level,
59 log_file,
60 max_size,
61 level_filter: RwLock::new(HashMap::new()),
62 fmt_cache: Mutex::new(VecDeque::new()),
63 filter,
64 logger_data: Mutex::new(logger_data),
65 show_process_id,
66 show_thread_info,
67 show_module_path,
68 highlight_keywords,
69 quickwit_client,
70 }
71 }
72
73 pub fn write(&self, msg: &[u8]) {
75 let mut logger_data = match self.logger_data.lock() {
76 Ok(v) => v,
77 Err(e) => {
78 eprint!("log mutex lock failed: {e:?}");
79 return;
80 }
81 };
82
83 if let Some(ref mut console) = logger_data.console {
85 console.write_all(msg).expect("write log to console fail");
86 }
87
88 if logger_data.log_size > self.max_size {
90 let mut log_file_closed = false;
91
92 if let Some(ref mut fileout) = logger_data.fileout {
94 fileout.flush().expect("flush log file fail");
95 logger_data.fileout.take();
96 log_file_closed = true;
97 }
98
99 if log_file_closed {
101 let bak = format!("{}.bak", self.log_file);
103 std::fs::remove_file(&bak).unwrap_or_default();
104 std::fs::rename(&self.log_file, &bak).expect("backup log file fail");
105
106 match crate::utils::open_log_file_sync(&self.log_file) {
107 Ok((writer, _)) => {
108 logger_data.fileout = Some(writer);
109 },
110 Err(e) => {
111 eprintln!("Failed to reopen log file {}: {}", self.log_file, e);
112 return;
113 }
114 }
115 logger_data.log_size = 0;
116 }
117 }
118
119 if let Some(ref mut fileout) = logger_data.fileout {
120 let ws = crate::utils::write_text(fileout, msg).unwrap();
121 logger_data.log_size += ws as u32;
122 }
123
124 if let Some(plugin) = &mut logger_data.plugin {
125 plugin.write_all(msg).expect("write log to plugin fail");
126 }
127 }
128
129 pub fn flush_inner(&self) {
131 let mut logger_data = match self.logger_data.lock() {
132 Ok(v) => v,
133 Err(e) => {
134 eprint!("log mutex lock failed: {e:?}");
135 return;
136 }
137 };
138
139 if let Some(ref mut console) = logger_data.console {
140 if let Err(e) = crate::utils::safe_flush(console) {
141 eprintln!("Failed to flush console: {}", e);
142 }
143 }
144
145 if let Some(ref mut fileout) = logger_data.fileout {
146 if let Err(e) = crate::utils::safe_flush(fileout) {
147 eprintln!("Failed to flush log file: {}", e);
148 }
149 }
150 }
151
152 fn apply_keyword_highlighting(&self, message: &str) -> String {
154 if self.highlight_keywords.is_empty() {
155 return message.to_string();
156 }
157
158 let mut result = message.to_string();
159 for keyword in &self.highlight_keywords {
160 if message.contains(keyword) {
161 let highlighted = format!("\x1b[41m{}\x1b[0m", keyword);
162 result = result.replace(keyword, &highlighted);
163 }
164 }
165 result
166 }
167}
168
169impl log::Log for LogPlus {
170 fn enabled(&self, metadata: &log::Metadata) -> bool {
171 if metadata.level() <= self.level {
172 if let Ok(level_filters) = self.level_filter.read() {
173 let mut target = metadata.target();
174 while !target.is_empty() {
175 if let Some(level) = level_filters.get(target) {
176 return metadata.level() <= *level;
177 }
178
179 target = match target.rfind("::") {
180 Some(rpos) => &target[..rpos],
181 None => ""
182 };
183 }
184 return true;
185 }
186 }
187 false
188 }
189
190 fn log(&self, record: &log::Record) {
191 if !self.enabled(record.metadata()) { return; }
192 if let Some(filter) = &self.filter {
193 if !filter.enabled(record) { return; }
194 }
195
196 let now = std::time::SystemTime::now()
197 .duration_since(std::time::UNIX_EPOCH)
198 .unwrap()
199 .as_secs();
200 let now = format!("{}", now);
201
202 let mut msg = get_msg_from_cache();
203
204 let is_detail = self.level >= log::LevelFilter::Debug;
206 let log_level = record.level();
207
208 if is_detail {
209 write!(&mut msg, "\x1b[90m{}\x1b[0m {}{:>5}\x1b[0m",
211 now, level_color(log_level), log_level
212 ).unwrap();
213
214 if self.show_process_id {
216 write!(&mut msg, " \x1b[90m[{}]\x1b[0m", std::process::id()).unwrap();
217 }
218
219 if self.show_thread_info {
221 if let Some(thread_name) = std::thread::current().name() {
222 write!(&mut msg, " \x1b[90m[{}]\x1b[0m", thread_name).unwrap();
223 } else {
224 write!(&mut msg, " \x1b[90m[{:?}]\x1b[0m", std::thread::current().id()).unwrap();
225 }
226 }
227
228 if self.show_module_path {
230 let target = record.target();
231 let line = record.line().unwrap_or(0);
232 write!(&mut msg, " \x1b[90m{}:{}\x1b[0m", target, line).unwrap();
233 }
234
235 let message = format!("{}", record.args());
237 let highlighted_message = self.apply_keyword_highlighting(&message);
238 write!(&mut msg, " ▶ {}\n", highlighted_message).unwrap();
239 } else {
240 let message = format!("{}", record.args());
242 let highlighted_message = self.apply_keyword_highlighting(&message);
243 write!(&mut msg, "{} {:>5} ▶ {}\n", now, log_level, highlighted_message).unwrap();
244 }
245
246 match self.logger_data.lock() {
248 Ok(logger_data) => {
249 if let Some(ref sender) = logger_data.sender {
250 sender.send(AsyncLogType::Message(msg)).unwrap();
252
253 if let Some(ref _quickwit_client) = self.quickwit_client {
255 let log_entry = QuickwitLogEntry {
256 timestamp: std::time::SystemTime::now()
257 .duration_since(std::time::UNIX_EPOCH)
258 .unwrap()
259 .as_secs(),
260 level: record.level().to_string(),
261 message: format!("{}", record.args()),
262 module: record.module_path().map(|s| s.to_string()),
263 file: record.file().map(|s| s.to_string()),
264 line: record.line(),
265 process_id: Some(std::process::id()),
266 thread_id: std::thread::current().name().map(|s| s.to_string()),
267 custom_fields: std::collections::HashMap::new(),
268 };
269
270 if let Err(e) = sender.send(AsyncLogType::QuickwitLog(log_entry)) {
272 eprintln!("✗ 发送 Quickwit 日志到异步处理器失败: {}", e);
273 }
274 }
275 return;
276 }
277 },
278 Err(e) => {
279 eprint!("log mutex lock failed: {e:?}");
280 return;
281 }
282 }
283
284 self.write(&msg);
286 put_msg_to_cache(msg.clone());
287
288 if let Some(ref quickwit_client) = self.quickwit_client {
290 let log_entry = QuickwitLogEntry {
291 timestamp: std::time::SystemTime::now()
292 .duration_since(std::time::UNIX_EPOCH)
293 .unwrap()
294 .as_secs(),
295 level: record.level().to_string(),
296 message: format!("{}", record.args()),
297 module: record.module_path().map(|s| s.to_string()),
298 file: record.file().map(|s| s.to_string()),
299 line: record.line(),
300 process_id: Some(std::process::id()),
301 thread_id: std::thread::current().name().map(|s| s.to_string()),
302 custom_fields: std::collections::HashMap::new(),
303 };
304
305 match quickwit_client.send_log(&log_entry) {
307 Ok(()) => {
308 if std::env::var("QUICKWIT_DEBUG").is_ok() {
310 eprintln!("✓ 日志已发送到 Quickwit: {}", log_entry.message);
311 }
312 },
313 Err(e) => {
314 eprintln!("✗ 发送日志到 Quickwit 失败: {}", e);
315 eprintln!(" 日志内容: {}", log_entry.message);
316 eprintln!(" 错误详情: {:?}", e);
317 }
318 }
319 }
320 }
321
322 fn flush(&self) {
323 if let Ok(logger_data) = self.logger_data.lock() {
324 if let Some(ref sender) = logger_data.sender {
325 if let Err(e) = sender.send(AsyncLogType::Flush) {
326 eprint!("failed in log::flush: {e:?}");
327 }
328 } else {
329 drop(logger_data);
330 self.flush_inner();
331 }
332 }
333 }
334}
335
336impl<F: Fn(&log::Record) -> bool + Send + Sync + 'static> CustomFilter for F {
338 fn enabled(&self, record: &log::Record) -> bool {
339 self(record)
340 }
341}