log_init 0.1.39

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

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

use crate::kv::KvRef;

thread_local! {
  static LAST_TIME: Cell<(u64, [u8; 19])> = const { Cell::new((0, [0u8; 19])) };
}

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

pub fn level_color(level: Level) -> ColoredString {
  let name = level.name();
  match level {
    Level::Error
    | Level::Error2
    | Level::Error3
    | Level::Error4
    | Level::Fatal
    | Level::Fatal2
    | Level::Fatal3
    | Level::Fatal4 => name.red(),
    Level::Warn | Level::Warn2 | Level::Warn3 | Level::Warn4 => name.yellow(),
    Level::Info | Level::Info2 | Level::Info3 | Level::Info4 => name.green(),
    Level::Debug | Level::Debug2 | Level::Debug3 | Level::Debug4 => name.blue(),
    Level::Trace | Level::Trace2 | Level::Trace3 | Level::Trace4 => name.magenta(),
  }
}

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

struct ArrayWriter {
  buf: [u8; 19],
  pos: usize,
}

impl Write for ArrayWriter {
  fn write_str(&mut self, s: &str) -> std::fmt::Result {
    let bytes = s.as_bytes();
    if self.pos + bytes.len() <= 19 {
      self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
      self.pos += bytes.len();
      Ok(())
    } else {
      Err(std::fmt::Error)
    }
  }
}

fn write_timestamp(buf: &mut String, ts: u64) {
  LAST_TIME.with(|cell| {
    let (cached_ts, cached_bytes) = cell.get();
    if cached_ts == ts
      && cached_ts != 0
      && let Ok(s) = std::str::from_utf8(&cached_bytes)
    {
      buf.push_str(s);
      return;
    }

    if let Ok(ts_i64) = i64::try_from(ts)
      && let Ok(timestamp) = jiff::Timestamp::from_second(ts_i64)
    {
      let zoned = jiff::Zoned::new(timestamp, crate::TZ.clone());
      let mut writer = ArrayWriter {
        buf: [0u8; 19],
        pos: 0,
      };
      if write!(writer, "{}", zoned.strftime("%Y-%m-%d %H:%M:%S")).is_ok()
        && writer.pos == 19
        && let Ok(s) = std::str::from_utf8(&writer.buf)
      {
        cell.set((ts, writer.buf));
        buf.push_str(s);
        return;
      }
    }

    let _ = write!(buf, "{ts}");
  });
}

impl Layout for Text {
  fn format(
    &self,
    record: &Record<'_>,
    diagnostics: &[Box<dyn Diagnostic>],
  ) -> Result<Vec<u8>, LogforthError> {
    let mut buf = String::with_capacity(128);
    let level = record.level();
    let file = record.file().unwrap_or_else(|| record.target());

    if self.color {
      let level_str = level_color(level);
      if let Some(line) = record.line() {
        let _ = write!(
          buf,
          "{level_str} \x1b[90m{file}:{line}\x1b[0m {}",
          record.payload()
        );
      } else {
        let _ = write!(
          buf,
          "{level_str} \x1b[90m{file}\x1b[0m {}",
          record.payload()
        );
      }
    } else {
      let ts = coarsetime::Clock::now_since_epoch().as_secs();
      let _ = write!(buf, "{level} ");
      write_timestamp(&mut buf, ts);
      if let Some(line) = record.line() {
        let _ = write!(buf, " {file}:{line} {}", record.payload());
      } else {
        let _ = write!(buf, " {file} {}", record.payload());
      }
    }

    record.key_values().visit(&mut KvRef(&mut buf))?;
    for d in diagnostics {
      d.visit(&mut KvRef(&mut buf))?;
    }

    Ok(buf.into_bytes())
  }
}