1use std::{
2 collections::{HashMap, VecDeque},
3 io::Write, mem::MaybeUninit, str::FromStr, sync::Mutex
4};
5
6use std::sync::RwLock;
7
8#[cfg(feature = "time")]
9use time::format_description::OwnedFormatItem;
10
11#[cfg(not(feature = "tokio"))]
12use std::{fs::File, io::{LineWriter, Stdout}, sync::mpsc::Sender};
13
14#[cfg(feature = "tokio")]
15use tokio::{
16 fs::File,
17 io::{AsyncWriteExt, BufWriter, Stdout},
18 sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}
19};
20
21const CACHE_STR_ARRAY_SIZE: usize = 16;
22const CACHE_STR_INIT_SIZE: usize = 256;
23
24static mut LOGGER_PLUS: MaybeUninit<LogPlus> = MaybeUninit::uninit();
25#[cfg(debug_assertions)]
26static mut INITED: bool = false;
27
28
29type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
30type BoxPlugin = Box<dyn std::io::Write + Send + Sync + 'static>;
31type BoxCustomFilter = Box<dyn CustomFilter>;
32type LevelFilter = RwLock<HashMap<String, log::LevelFilter>>;
33
34
35pub struct Builder {
67 level: log::LevelFilter,
68 log_file: String,
69 log_file_max: u32,
70 use_console: bool,
71 use_async: bool,
72 plugin: Option<BoxPlugin>,
73 filter: Option<BoxCustomFilter>,
74}
75
76pub trait CustomFilter: Send + Sync + 'static {
78 fn enabled(&self, record: &log::Record) -> bool;
79}
80
81struct LogPlus {
82 level: log::LevelFilter, #[cfg(feature = "time")]
84 dt_fmt: OwnedFormatItem, #[cfg(feature = "chrono")]
86 dt_fmt: String, log_file: String, max_size: u32, level_filter: LevelFilter, fmt_cache: Mutex<VecDeque<Vec<u8>>>, #[cfg(feature = "tokio")]
92 msg_tx: UnboundedSender<AsyncLogType>, filter: Option<BoxCustomFilter>, #[cfg(not(feature = "tokio"))]
95 logger_data: Mutex<LogData>, }
97
98struct LogData {
99 log_size: u32, #[cfg(feature = "tokio")]
101 use_file: bool, #[cfg(feature = "tokio")]
103 console: Option<BufWriter<Stdout>>, #[cfg(not(feature = "tokio"))]
105 console: Option<LineWriter<Stdout>>, #[cfg(feature = "tokio")]
107 fileout: Option<BufWriter<File>>, #[cfg(not(feature = "tokio"))]
109 fileout: Option<LineWriter<File>>, #[cfg(not(feature = "tokio"))]
111 sender: Option<Sender<AsyncLogType>>, plugin: Option<BoxPlugin>, }
114
115struct SkipAnsiColorIter<'a> {
116 data: &'a [u8],
117 pos: usize,
118 find_len: usize,
119}
120
121enum AsyncLogType {
122 Message(Vec<u8>),
123 Flush,
124}
125
126
127#[inline]
128pub fn init_log_simple(level: &str, log_file: String, log_file_max: &str,
129 use_console: bool, use_async: bool) -> Result<()> {
130 init_log_inner(parse_level(level)?, log_file, parse_size(log_file_max)?,
131 use_console, use_async, None, None)
132}
133
134#[inline]
155pub fn init_log(level: log::LevelFilter, log_file: String, log_file_max: u32,
156 use_console: bool, use_async: bool) -> Result<()> {
157 init_log_inner(level, log_file, log_file_max, use_console, use_async, None, None)
158}
159
160#[inline]
161pub fn init_log_with_plugin<P>(level: log::LevelFilter, log_file: String,
162 log_file_max: u32, use_console: bool, use_async: bool,
163 plugin: P) -> Result<()>
164where
165 P: std::io::Write + Send + Sync + 'static,
166{
167 init_log_inner(level, log_file, log_file_max, use_console, use_async,
168 Some(Box::new(plugin)), None)
169}
170
171#[inline]
172pub fn init_log_with_filter(level: log::LevelFilter, log_file: String,
173 log_file_max: u32, use_console: bool, use_async: bool,
174 filter: impl CustomFilter) -> Result<()> {
175 init_log_inner(level, log_file, log_file_max, use_console, use_async,
176 None, Some(Box::new(filter)))
177}
178
179pub fn init_log_with_all<P>(level: log::LevelFilter, log_file: String,
180 log_file_max: u32, use_console: bool, use_async: bool,
181 plugin: P, filter: impl CustomFilter) -> Result<()>
182where
183 P: std::io::Write + Send + Sync + 'static,
184{
185 init_log_inner(level, log_file, log_file_max, use_console, use_async,
186 Some(Box::new(plugin)), Some(Box::new(filter)))
187}
188
189pub fn set_level(target: String, level: log::LevelFilter) {
196 if let Ok(mut f) = get_logger_plus().level_filter.write() {
197 f.insert(target, level);
198 }
199}
200
201pub fn parse_level(level: &str) -> Result<log::LevelFilter> {
211 match log::LevelFilter::from_str(level) {
212 Ok(num) => Ok(num),
213 Err(_) => Err(format!("can't parse log level: {level}").into()),
214 }
215}
216
217pub fn parse_size(size: &str) -> Result<u32> {
227 match size.parse() {
228 Ok(n) => Ok(n),
229 Err(_) => match size[..size.len() - 1].parse() {
230 Ok(n) => {
231 let s = size.as_bytes();
232 match s[s.len() - 1] {
233 b'b' | b'B' => Ok(n),
234 b'k' | b'K' => Ok(n * 1024),
235 b'm' | b'M' => Ok(n * 1024 * 1024),
236 b'g' | b'G' => Ok(n * 1024 * 1024 * 1024),
237 _ => Err(format!("parse size error, unit is unknown: {size}").into()),
238 }
239 },
240 Err(e) => Err(e.into()),
241 }
242 }
243}
244
245
246#[cfg(not(feature = "tokio"))]
247impl LogPlus {
248 fn write(&self, msg: &[u8]) {
250 let mut logger_data = match self.logger_data.lock() {
251 Ok(v) => v,
252 Err(e) => {
253 eprint!("log mutex lock failed: {e:?}");
254 return;
255 }
256 };
257
258 if let Some(ref mut console) = logger_data.console {
260 console.write_all(msg).expect("write log to console fail");
261 }
262
263 if logger_data.log_size > self.max_size {
265 let mut log_file_closed = false;
266
267 if let Some(ref mut fileout) = logger_data.fileout {
269 fileout.flush().expect("flush log file fail");
270 logger_data.fileout.take();
271 log_file_closed = true;
272 }
273
274 if log_file_closed {
276 let bak = format!("{}.bak", self.log_file);
278 std::fs::remove_file(&bak).unwrap_or_default();
279 std::fs::rename(&self.log_file, &bak).expect("backup log file fail");
280
281 let f = std::fs::OpenOptions::new()
282 .write(true)
283 .create(true)
284 .open(&self.log_file)
285 .expect("reopen log file fail");
286
287 logger_data.fileout = Some(LineWriter::new(f));
288 logger_data.log_size = 0;
289 }
290 }
291
292 if let Some(ref mut fileout) = logger_data.fileout {
293 let ws = write_text(fileout, msg).unwrap();
294 logger_data.log_size += ws as u32;
295 }
296
297 if let Some(plugin) = &mut logger_data.plugin {
298 plugin.write_all(msg).expect("write log to plugin fail");
299 }
300 }
301
302 fn flush_inner(&self) {
304 let mut logger_data = match self.logger_data.lock() {
305 Ok(v) => v,
306 Err(e) => {
307 eprint!("log mutex lock failed: {e:?}");
308 return;
309 }
310 };
311
312 if let Some(ref mut console) = logger_data.console {
313 console.flush().expect("flush log console error");
314 }
315
316 if let Some(ref mut fileout) = logger_data.fileout {
317 fileout.flush().expect("flush log file error");
318 }
319 }
320
321}
322
323
324impl log::Log for LogPlus {
325 fn enabled(&self, metadata: &log::Metadata) -> bool {
326 if metadata.level() <= self.level {
327 if let Ok(level_filters) = self.level_filter.read() {
328 let mut target = metadata.target();
329 while !target.is_empty() {
330 if let Some(level) = level_filters.get(target) {
331 return metadata.level() <= *level;
332 }
333
334 target = match target.rfind("::") {
335 Some(rpos) => &target[..rpos],
336 None => ""
337 };
338 }
339 return true;
340 }
341 }
342 false
343 }
344
345 #[cfg(feature = "tokio")]
346 fn log(&self, record: &log::Record) {
347 if !self.enabled(record.metadata()) { return; }
348 if let Some(filter) = &self.filter {
349 if !filter.enabled(record) { return; }
350 }
351
352 #[cfg(feature = "time")]
353 unsafe { time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound); }
354 #[cfg(feature = "time")]
355 let now = time::OffsetDateTime::now_local().unwrap().format(&self.dt_fmt).unwrap();
356 #[cfg(feature = "chrono")]
357 let now = chrono::Local::now().format(&self.dt_fmt);
358
359 let mut msg = get_msg_from_cache();
360 let is_detail = self.level >= log::LevelFilter::Debug;
361 let log_level = record.level();
362
363 if is_detail {
365 write!(&mut msg, "[\x1b[36m{now}\x1b[0m] [{}{log_level:5}\x1b[0m]",
366 level_color(log_level),
367 ).unwrap();
368 } else {
369 write!(&mut msg, "[{now}] [{log_level:5}]").unwrap();
370 }
371 if let Some(task_id) = tokio::task::try_id() {
372 write!(&mut msg, " [TASK-{task_id}]").unwrap();
373 }
374 if is_detail {
375 write!(&mut msg, " [{}::{}]", record.target(), record.line().unwrap_or(0)).unwrap();
376 };
377 write!(&mut msg, " - {}\n", record.args()).unwrap();
378
379 if let Err(e) = get_logger_plus().msg_tx.send(AsyncLogType::Message(msg)) {
381 eprintln!("failed in log::Log.log, {e:?}");
382 }
383
384 }
385
386 #[cfg(not(feature = "tokio"))]
387 fn log(&self, record: &log::Record) {
388 if !self.enabled(record.metadata()) { return; }
389 if let Some(filter) = &self.filter {
390 if !filter.enabled(record) { return; }
391 }
392
393 #[cfg(feature = "time")]
394 unsafe { time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound); }
395 #[cfg(feature = "time")]
396 let now = time::OffsetDateTime::now_local().unwrap().format(&self.dt_fmt).unwrap();
397 #[cfg(feature = "chrono")]
398 let now = chrono::Local::now().format(&self.dt_fmt);
399
400 let mut msg = get_msg_from_cache();
401
402 if self.level >= log::LevelFilter::Debug {
404 write!(&mut msg, "[\x1b[36m{now}\x1b[0m] [{}{:5}\x1b[0m] [{}::{}] - {}\n",
405 level_color(record.level()),
406 record.level(),
407 record.target(),
408 record.line().unwrap_or(0),
409 record.args()).unwrap();
410 } else {
411 write!(&mut msg, "[{now}] [{:5}] - {}\n", record.level(), record.args()).unwrap();
412 };
413
414 match self.logger_data.lock() {
416 Ok(logger_data) => {
417 if let Some(ref sender) = logger_data.sender {
418 sender.send(AsyncLogType::Message(msg)).unwrap();
420 return;
421 }
422 },
423 Err(e) => {
424 eprint!("log mutex lock failed: {e:?}");
425 return;
426 }
427 }
428
429 self.write(&msg);
431 put_msg_to_cache(msg);
432 }
433
434 #[cfg(feature = "tokio")]
435 fn flush(&self) {
436 tokio::spawn(async move {
437 if let Err(e) = get_logger_plus().msg_tx.send(AsyncLogType::Flush) {
438 eprintln!("failed in log send to channel: {e:?}");
439 }
440 });
441 }
442
443 #[cfg(not(feature = "tokio"))]
444 fn flush(&self) {
445 if let Ok(logger_data) = self.logger_data.lock() {
446 if let Some(ref sender) = logger_data.sender {
447 if let Err(e) = sender.send(AsyncLogType::Flush) {
448 eprint!("failed in log::flush: {e:?}");
449 }
450 } else {
451 drop(logger_data);
452 self.flush_inner();
453 }
454 }
455 }
456
457}
458
459
460impl Builder {
461 #[inline]
462 pub fn new() -> Self {
463 Self {
464 level: log::LevelFilter::Info,
465 log_file: String::new(),
466 log_file_max: 10 * 1024 * 1024,
467 use_console: true,
468 use_async: true,
469 plugin: None,
470 filter: None,
471 }
472 }
473
474 #[inline]
475 pub fn builder(self) -> Result<()> {
476 init_log_inner(self.level, self.log_file, self.log_file_max,
477 self.use_console, self.use_async, self.plugin, self.filter)
478 }
479
480 #[inline]
481 pub fn level(mut self, level: log::LevelFilter) -> Self {
482 self.level = level;
483 self
484 }
485
486 #[inline]
487 pub fn log_file(mut self, log_file: String) -> Self {
488 self.log_file = log_file;
489 self
490 }
491
492 #[inline]
493 pub fn log_file_max(mut self, log_file_max: u32) -> Self {
494 self.log_file_max = log_file_max;
495 self
496 }
497
498 #[inline]
499 pub fn use_console(mut self, use_console: bool) -> Self {
500 self.use_console = use_console;
501 self
502 }
503
504 #[inline]
505 pub fn use_async(mut self, use_async: bool) -> Self {
506 self.use_async = use_async;
507 self
508 }
509
510 #[inline]
511 pub fn level_str(mut self, level: &str) -> Result<Self> {
512 self.level = parse_level(level)?;
513 Ok(self)
514 }
515
516 #[inline]
517 pub fn log_file_max_str(mut self, log_file_max: &str) -> Result<Self> {
518 self.log_file_max = parse_size(log_file_max)?;
519 Ok(self)
520 }
521
522 pub fn plugin<T: std::io::Write + Send + Sync + 'static>(mut self, plugin: T) -> Self {
523 self.plugin = Some(Box::new(plugin));
524 self
525 }
526
527 pub fn filter(mut self, filter: impl CustomFilter) -> Self {
528 self.filter = Some(Box::new(filter));
529 self
530 }
531}
532
533impl<F: Fn(&log::Record) -> bool + Send + Sync + 'static> CustomFilter for F {
534 fn enabled(&self, record: &log::Record) -> bool {
535 self(record)
536 }
537}
538
539impl<'a> SkipAnsiColorIter<'a> {
540 pub fn new(data: &'a [u8]) -> Self {
541 let find_len = if data.len() > 3 {
542 data.len() - 3
543 } else {
544 0
545 };
546
547 SkipAnsiColorIter {
548 data,
549 pos: 0,
550 find_len,
551 }
552 }
553}
554
555
556impl<'a> Iterator for SkipAnsiColorIter<'a> {
557 type Item = &'a [u8];
558
559 #[inline]
560 fn next(&mut self) -> Option<Self::Item> {
561 let (mut pos, find_len, data) = (self.pos, self.find_len, self.data);
563 while pos < find_len {
564 unsafe {
565 if *data.get_unchecked(pos) != 0x1b || *data.get_unchecked(pos + 1) != b'[' {
566 pos += 1;
567 continue;
568 }
569
570 let n = if *data.get_unchecked(pos + 3) == b'm' { 4 } else { 5 };
572 let p = self.pos;
573 self.pos = pos + n;
574 return Some(&data[p..pos]);
575 }
576 }
577
578 let dl = data.len();
580 if pos < dl {
581 let p = self.pos;
582 self.pos = dl;
583 return Some(&data[p..dl]);
584 }
585
586 None
587 }
588}
589
590#[cfg(feature = "tokio")]
591pub fn init_log_inner(
592 level: log::LevelFilter,
593 log_file: String,
594 log_file_max: u32,
595 use_console: bool,
596 _use_async: bool,
597 plugin: Option<BoxPlugin>,
598 filter: Option<BoxCustomFilter>
599) -> Result<()>
600{
601
602 #[cfg(debug_assertions)]
603 debug_check_init();
604
605 log::set_max_level(level);
606
607 #[cfg(feature = "time")]
608 let dt_fmt = "[year]-[month]-[day] [hour]:[minute]:[second]";
614 #[cfg(feature = "time")]
615 let dt_fmt = time::format_description::parse_owned::<2>(dt_fmt).unwrap();
616
617 #[cfg(feature = "chrono")]
618 let dt_fmt = if level >= log::LevelFilter::Debug {
619 "%m-%d %H:%M:%S"
620 } else {
621 "%Y-%m-%d %H:%M:%S"
622 }.to_owned();
623
624 let use_file = !log_file.is_empty();
625 let console = if use_console {
626 Some(BufWriter::new(tokio::io::stdout()))
627 } else {
628 None
629 };
630
631 let (tx, rx) = unbounded_channel();
632
633 let logger = LogPlus {
634 level,
635 dt_fmt,
636 log_file,
637 max_size: log_file_max,
638 level_filter: RwLock::new(HashMap::new()),
639 fmt_cache: Mutex::new(VecDeque::with_capacity(CACHE_STR_ARRAY_SIZE)),
640 msg_tx: tx,
641 filter,
642 };
643
644 unsafe {
645 #[cfg(debug_assertions)]
646 {
647 debug_assert!(!INITED);
648 INITED = true;
649 }
650 LOGGER_PLUS.write(logger);
651 }
652
653 log::set_logger(get_logger_plus()).expect("init_log call set_logger error");
655
656 tokio::spawn(write_async(LogData {
657 log_size: 0,
658 use_file,
659 console,
660 fileout: None,
661 plugin,
662 }, rx));
663
664 Ok(())
665}
666
667#[cfg(not(feature = "tokio"))]
668fn init_log_inner(
669 level: log::LevelFilter,
670 log_file: String,
671 log_file_max: u32,
672 use_console: bool,
673 use_async: bool,
674 plugin: Option<BoxPlugin>,
675 filter: Option<BoxCustomFilter>
676) -> Result<()>
677{
678
679 #[cfg(debug_assertions)]
680 debug_check_init();
681
682 log::set_max_level(level);
683
684 #[cfg(feature = "time")]
685 let dt_fmt = if level >= log::LevelFilter::Debug {
686 "[month]-[day] [hour]:[minute]:[second]"
687 } else {
688 "[year]-[month]-[day] [hour]:[minute]:[second]"
689 };
690 #[cfg(feature = "time")]
691 let dt_fmt = time::format_description::parse_owned::<2>(dt_fmt).unwrap();
692
693 #[cfg(feature = "chrono")]
694 let dt_fmt = if level >= log::LevelFilter::Debug {
695 "%m-%d %H:%M:%S"
696 } else {
697 "%Y-%m-%d %H:%M:%S"
698 }.to_owned();
699
700 let console = if use_console {
702 Some(LineWriter::new(std::io::stdout()))
703 } else {
704 None
705 };
706
707 let (fileout, log_size) = if !log_file.is_empty() {
709 let f = std::fs::OpenOptions::new()
710 .append(true)
711 .create(true)
712 .open(&log_file)?;
713 let log_size = std::fs::metadata(&log_file)?.len() as u32;
714 let fileout = Some(LineWriter::new(f));
715 (fileout, log_size)
716 } else {
717 (None, 0)
718 };
719
720 let sender = if use_async {
722 let (sender, receiver) = std::sync::mpsc::channel::<AsyncLogType>();
723 std::thread::spawn(move || loop {
724 match receiver.recv() {
725 Ok(data) => match data {
726 AsyncLogType::Message(msg) => {
727 get_logger_plus().write(&msg);
728 put_msg_to_cache(msg);
729 },
730 AsyncLogType::Flush => get_logger_plus().flush_inner(),
731 },
732 Err(e) => eprintln!("logger channel recv error: {}", e),
733 }
734 });
735 Some(sender)
736 } else {
737 None
738 };
739
740 let logger = LogPlus {
741 level,
742 dt_fmt,
743 log_file,
744 max_size: log_file_max,
745 level_filter: RwLock::new(HashMap::new()),
746 fmt_cache: Mutex::new(VecDeque::with_capacity(CACHE_STR_ARRAY_SIZE)),
747 filter,
748 logger_data: Mutex::new(LogData {
749 log_size,
750 console,
751 fileout,
752 sender,
753 plugin,
754 }),
755 };
756
757 unsafe {
758 #[cfg(debug_assertions)]
759 {
760 debug_assert!(!INITED);
761 INITED = true;
762 }
763 LOGGER_PLUS.write(logger);
764 }
765
766 log::set_logger(get_logger_plus()).expect("init_log call set_logger error");
768
769 Ok(())
770}
771
772
773#[cfg(not(feature = "tokio"))]
774fn write_text(w: &mut LineWriter<File>, msg: &[u8]) -> std::io::Result<usize> {
775 let mut write_len = 0;
776 for item in SkipAnsiColorIter::new(msg) {
777 write_len += item.len();
778 w.write_all(item)?;
779 }
780
781 let len = msg.len();
783 if len > 0 && (msg[len - 1] == b'\n' || msg[len - 1] == b'\r') {
784 w.flush()?;
785 }
786
787 Ok(write_len)
788}
789
790#[cfg(feature = "tokio")]
791async fn write_async(mut log_data: LogData, mut rx: UnboundedReceiver<AsyncLogType>) {
792 let logger_plus = get_logger_plus();
793
794 if log_data.use_file && log_data.fileout.is_none() {
796 let (f, size) = open_log_file(&logger_plus.log_file, true).await;
797 log_data.fileout = Some(f);
798 log_data.log_size = size;
799 }
800
801 while let Some(data) = rx.recv().await {
803 match data {
804 AsyncLogType::Message(msg) => {
805 write_to_log(&mut log_data, &msg).await;
807 put_msg_to_cache(msg);
809 }
810 AsyncLogType::Flush => {
811 if let Some(ref mut console) = log_data.console {
812 if let Err(e) = console.flush().await {
813 println!("flush log to console failed: {e:?}");
814 }
815 }
816 if let Some(ref mut fileout) = log_data.fileout {
817 if let Err(e) = fileout.flush().await {
818 println!("flush log to file failed: {e:?}");
819 }
820 }
821 }
822 }
823 }
824}
825
826#[cfg(feature = "tokio")]
827async fn write_text(w: &mut BufWriter<File>, msg: &[u8]) -> std::io::Result<usize> {
828 let mut write_len = 0;
829 for item in SkipAnsiColorIter::new(msg) {
830 write_len += item.len();
831 w.write_all(item).await?;
832 }
833
834 let len = msg.len();
836 if len > 0 && (msg[len - 1] == b'\n' || msg[len - 1] == b'\r') {
837 w.flush().await?;
838 }
839
840 Ok(write_len)
841}
842
843#[cfg(feature = "tokio")]
844async fn open_log_file(log_file: &str, append: bool) -> (BufWriter<File>, u32) {
845 let f = tokio::fs::OpenOptions::new()
846 .append(append)
847 .write(true)
848 .create(true)
849 .open(log_file)
850 .await
851 .expect("reopen log file fail");
852
853 let size = if append {
854 tokio::fs::metadata(log_file).await.map_or_else(|_| 0, |m| m.len())
855 } else {
856 0
857 };
858 return (BufWriter::new(f), size as u32);
859}
860
861#[cfg(feature = "tokio")]
862async fn write_to_log(log_data: &mut LogData, msg: &[u8]) {
863 if msg.is_empty() { return; }
864
865 if let Some(ref mut console) = log_data.console {
867 if console.write_all(&msg).await.is_ok() {
868 let c = msg[msg.len() - 1];
869 if c == b'\n' || c == b'\r' {
870 let _ = console.flush().await;
871 }
872 }
873 }
874
875 let logger_plus = get_logger_plus();
877 if log_data.log_size > logger_plus.max_size {
878 let has_file = match log_data.fileout {
879 Some(ref mut fileout) => {
880 if let Err(e) = fileout.flush().await {
882 eprintln!("failed in log flush log file: {e:?}");
883 }
884 true
885 }
886 None => false
887 };
888
889 if has_file {
890 log_data.fileout.take();
891
892 let bak = format!("{}.bak", logger_plus.log_file);
895 let _ = tokio::fs::remove_file(&bak).await;
896 match tokio::fs::rename(&logger_plus.log_file, &bak).await {
897 Ok(_) => {
898 log_data.fileout = Some(open_log_file(&logger_plus.log_file, false).await.0);
899 log_data.log_size = 0;
900 }
901 Err(e) => eprintln!("failed in log rename file: {e:?}"),
902 }
903 }
904 }
905
906 if let Some(ref mut fileout) = log_data.fileout {
907 match write_text(fileout, &msg).await {
908 Ok(size) => log_data.log_size += size as u32,
909 Err(e) => eprintln!("failed in write to file: {e:?}"),
910 }
911 }
912
913 if let Some(ref mut plugin) = log_data.plugin {
914 if let Err(e) = plugin.write_all(&msg) {
915 eprintln!("failed in log plugin: {e:?}");
916 }
917 }
918}
919
920fn get_logger_plus() -> &'static LogPlus {
921 unsafe {
922 #[cfg(debug_assertions)]
923 debug_assert!(INITED);
924
925 LOGGER_PLUS.assume_init_ref()
926 }
927}
928
929fn level_color(level: log::Level) -> &'static str {
931 const RED: &str = "\x1b[31m";
934 const GREEN: &str = "\x1b[32m";
935 const YELLOW: &str = "\x1b[33m";
936 const BLUE: &str = "\x1b[34m";
937 const MAGENTA: &str = "\x1b[35m";
938 match level {
942 log::Level::Trace => GREEN,
943 log::Level::Debug => YELLOW,
944 log::Level::Info => BLUE,
945 log::Level::Warn => MAGENTA,
946 log::Level::Error => RED,
947 }
948}
949
950fn get_msg_from_cache() -> Vec<u8> {
952 if let Ok(mut fmt_cache) = get_logger_plus().fmt_cache.lock() {
953 if let Some(vec) = fmt_cache.pop_back() {
954 return vec;
955 }
956 }
957
958 Vec::with_capacity(CACHE_STR_INIT_SIZE)
959}
960
961fn put_msg_to_cache(mut value: Vec<u8>) {
963 if value.capacity() <= CACHE_STR_INIT_SIZE {
964 value.clear();
965
966 if let Ok(mut fmt_cache) = get_logger_plus().fmt_cache.lock() {
967 if fmt_cache.len() < fmt_cache.capacity() {
968 fmt_cache.push_back(value);
969 }
970 }
971 }
972}
973
974#[cfg(debug_assertions)]
975fn debug_check_init() {
976 use std::sync::atomic::{AtomicBool, Ordering};
977
978 static INITED: AtomicBool = AtomicBool::new(false);
979
980 if let Err(true) = INITED.compare_exchange(false, true, Ordering::Release, Ordering::Relaxed) {
981 panic!("init_log must run once!");
982 }
983}