log_init 0.1.38

Smart logging initialization with automatic backend detection / 智能日志初始化与自动后端检测
Documentation
use std::{
  cell::{Cell, RefCell},
  io::{IsTerminal, stdin},
};

use colored::{ColoredString, Colorize};
use logforth::{
  Error as LogforthError, Layout,
  diagnostic::Diagnostic,
  record::{Level, Record},
};

use crate::Kv;

thread_local! {
  static LAST_TS: Cell<i64> = const { Cell::new(0) };
  static LAST_STR: RefCell<String> = const { RefCell::new(String::new()) };
}

#[derive(Debug)]
pub struct Text {
  pub color: bool,
}

pub fn level_color(level: Level) -> ColoredString {
  match level {
    Level::Error => level.to_string().red(),
    Level::Warn => level.to_string().yellow(),
    Level::Info => level.to_string().green(),
    Level::Debug => level.to_string().blue(),
    Level::Trace => level.to_string().magenta(),
    _ => level.to_string().into(),
  }
}

impl Default for Text {
  fn default() -> Self {
    Self {
      color: stdin().is_terminal(),
    }
  }
}

impl Layout for Text {
  fn format(
    &self,
    record: &Record<'_>,
    diagnostics: &[Box<dyn Diagnostic>],
  ) -> Result<Vec<u8>, LogforthError> {
    let level = record.level();

    let file = record.file().unwrap_or(record.target());
    let file_line = if let Some(line) = record.line() {
      format!("{file}:{line}")
    } else {
      file.into()
    };

    let msg = record.payload();

    let mut visitor = Kv {
      text: String::new(),
    };

    record.key_values().visit(&mut visitor)?;
    for d in diagnostics {
      d.visit(&mut visitor)?;
    }
    let msg = if visitor.text.is_empty() {
      msg.to_string()
    } else {
      format!("{} {}", msg, visitor.text)
    };

    let msg = if self.color {
      let level = level_color(level);
      let file_line = file_line.bright_black();
      format!("{level} {file_line} {msg}")
    } else {
      let ts = coarsetime::Clock::now_since_epoch().as_secs() as i64;
      let ts_str = LAST_TS.with(|last_ts_cell| {
        LAST_STR.with(|last_str_cell| {
          let last_ts = last_ts_cell.get();
          if last_ts == ts {
            // Return the cached formatted timestamp / 返回缓存的格式化时间戳
            last_str_cell.borrow().clone()
          } else {
            // Format new timestamp and update cache / 格式化新时间戳并更新缓存
            let new_str = if let Ok(timestamp) = jiff::Timestamp::from_second(ts) {
              jiff::Zoned::new(timestamp, crate::TZ.clone())
                .strftime("%Y-%m-%d %H:%M:%S")
                .to_string()
            } else {
              ts.to_string()
            };
            last_ts_cell.set(ts);
            *last_str_cell.borrow_mut() = new_str.clone();
            new_str
          }
        })
      });
      format!("{level} {ts_str} {file_line} {msg}")
    };

    Ok(msg.into_bytes())
  }
}