Files
ChuSQL/chusql-storage/src/log.rs
T
2026-09-24 21:28:49 +08:00

384 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::fmt;
use std::io::{IsTerminal, Write};
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::OnceLock;
// 极简日志:等级用原子变量,关掉的等级只花一次原子读。
// 颜色
/// 转义序列收尾
const RESET: &str = "\u{1b}[0m";
/// 加粗红(ERROR)
const BOLD_RED: &str = "\u{1b}[1m\u{1b}[31m";
/// 加粗橙(WARN)
const BOLD_ORANGE: &str = "\u{1b}[1m\u{1b}[38;5;208m";
/// 只加粗(DEBUG)
const BOLD: &str = "\u{1b}[1m";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
// 等级与来源
/// 日志等级
pub enum Level {
Off,
Error,
Warn,
Info,
Debug,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// 来源:服务自身或管道请求
pub enum Channel {
Core,
Pipe,
}
impl Channel {
/// 来源标签
fn tag(self) -> &'static str {
match self {
Channel::Core => "core",
Channel::Pipe => "pipe",
}
}
}
impl Level {
/// 解析等级名
pub fn parse(s: &str) -> Option<Level> {
match s.trim().to_ascii_lowercase().as_str() {
"off" => Some(Level::Off),
"error" => Some(Level::Error),
"warn" | "warning" => Some(Level::Warn),
"info" => Some(Level::Info),
"debug" => Some(Level::Debug),
_ => None,
}
}
/// 等级名
pub fn name(self) -> &'static str {
match self {
Level::Off => "off",
Level::Error => "error",
Level::Warn => "warn",
Level::Info => "info",
Level::Debug => "debug",
}
}
/// 行里的等级标签
fn tag(self) -> &'static str {
match self {
Level::Error => "ERROR",
Level::Warn => "WARN ",
Level::Info => "INFO ",
_ => "DEBUG",
}
}
/// 等级的颜色
fn color(self) -> &'static str {
match self {
Level::Error => BOLD_RED,
Level::Warn => BOLD_ORANGE,
Level::Debug => BOLD,
_ => "",
}
}
}
static LEVEL: AtomicU8 = AtomicU8::new(Level::Info as u8);
static COLOR: OnceLock<bool> = OnceLock::new();
// 写入
/// 设置全局等级
pub fn set_level(level: Level) {
LEVEL.store(level as u8, Ordering::Relaxed);
}
#[inline]
/// 这个等级要不要输出
pub fn enabled(level: Level) -> bool {
level as u8 <= LEVEL.load(Ordering::Relaxed)
}
/// 启动时调一次
pub fn init() {
enable_vt();
}
/// 写出一行
pub fn write(channel: Channel, level: Level, args: fmt::Arguments) {
let msg = args.to_string();
let line = render(level, channel, color_enabled(), &now(), &msg);
let stderr = std::io::stderr();
let mut out = stderr.lock();
let _ = writeln!(out, "{}", line);
}
/// 是否着色(终端或强制)
fn color_enabled() -> bool {
*COLOR.get_or_init(|| match std::env::var("CHUSQL_LOG_COLOR").as_deref().map(str::trim) {
Ok("always") | Ok("yes") | Ok("1") => true,
Ok("never") | Ok("no") | Ok("0") => false,
_ => std::io::stderr().is_terminal(),
})
}
/// 拼一行(纯函数,可测)
fn render(level: Level, channel: Channel, color: bool, stamp: &Stamp, msg: &str) -> String {
let tag = level.tag();
let paint = level.color();
let level_text = if color && !paint.is_empty() {
format!("{}{}{}", paint, tag, RESET)
} else {
tag.to_string()
};
format!("[{} {}] [{}] {}", level_text, stamp, channel.tag(), msg)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
// 时间
/// 本地日期时间
pub struct Stamp {
pub year: u16,
pub month: u16,
pub day: u16,
pub hour: u16,
pub minute: u16,
pub second: u16,
pub milli: u16,
}
/// 格式:年-月-日 时:分:秒.毫秒
impl fmt::Display for Stamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}",
self.year, self.month, self.day, self.hour, self.minute, self.second, self.milli
)
}
}
/// 当前本地时间(Windows)
#[cfg(windows)]
fn now() -> Stamp {
#[repr(C)]
struct SystemTime {
year: u16,
month: u16,
day_of_week: u16,
day: u16,
hour: u16,
minute: u16,
second: u16,
milliseconds: u16,
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetLocalTime(out: *mut SystemTime);
}
let mut t = SystemTime {
year: 0,
month: 0,
day_of_week: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
milliseconds: 0,
};
unsafe { GetLocalTime(&mut t) };
Stamp {
year: t.year,
month: t.month,
day: t.day,
hour: t.hour,
minute: t.minute,
second: t.second,
milli: t.milliseconds,
}
}
/// 当前时间(其他平台用 UTC)
#[cfg(not(windows))]
fn now() -> Stamp {
let d = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let secs = d.as_secs();
let (year, month, day) = civil_from_days((secs / 86_400) as i64);
Stamp {
year: year as u16,
month: month as u16,
day: day as u16,
hour: ((secs % 86_400) / 3600) as u16,
minute: ((secs % 3_600) / 60) as u16,
second: (secs % 60) as u16,
milli: d.subsec_millis() as u16,
}
}
/// 天数换算成日期
pub fn civil_from_days(days: i64) -> (i64, u16, u16) {
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as u16;
let month = if mp < 10 { mp + 3 } else { mp - 9 } as u16;
(y + i64::from(month <= 2), month, day)
}
// 终端
/// 打开 Windows 的 VT 处理
#[cfg(windows)]
fn enable_vt() {
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetStdHandle(which: u32) -> *mut core::ffi::c_void;
fn GetConsoleMode(handle: *mut core::ffi::c_void, mode: *mut u32) -> i32;
fn SetConsoleMode(handle: *mut core::ffi::c_void, mode: u32) -> i32;
}
const STD_ERROR_HANDLE: u32 = 0xFFFF_FFF4;
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
unsafe {
let handle = GetStdHandle(STD_ERROR_HANDLE);
let mut mode = 0u32;
if GetConsoleMode(handle, &mut mode) != 0 {
let _ = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
}
/// 其他平台什么都不用做
#[cfg(not(windows))]
fn enable_vt() {}
// 宏
/// debug 日志
#[macro_export]
macro_rules! log_debug {
(core, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Core, $crate::log::Level::Debug, $($arg)*)
};
(pipe, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Pipe, $crate::log::Level::Debug, $($arg)*)
};
}
/// info 日志
#[macro_export]
macro_rules! log_info {
(core, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Core, $crate::log::Level::Info, $($arg)*)
};
(pipe, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Pipe, $crate::log::Level::Info, $($arg)*)
};
}
/// warn 日志
#[macro_export]
macro_rules! log_warn {
(core, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Core, $crate::log::Level::Warn, $($arg)*)
};
(pipe, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Pipe, $crate::log::Level::Warn, $($arg)*)
};
}
/// error 日志
#[macro_export]
macro_rules! log_error {
(core, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Core, $crate::log::Level::Error, $($arg)*)
};
(pipe, $($arg:tt)*) => {
$crate::log_line!($crate::log::Channel::Pipe, $crate::log::Level::Error, $($arg)*)
};
}
/// 四个宏的公共部分
#[doc(hidden)]
#[macro_export]
macro_rules! log_line {
($chan:expr, $lvl:expr, $($arg:tt)*) => {
if $crate::log::enabled($lvl) {
$crate::log::write($chan, $lvl, format_args!($($arg)*))
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn civil_from_days_known_dates() {
assert_eq!(civil_from_days(0), (1970, 1, 1));
assert_eq!(civil_from_days(10_957), (2000, 1, 1));
assert_eq!(civil_from_days(11_016), (2000, 2, 29));
assert_eq!(civil_from_days(19_723), (2024, 1, 1));
assert_eq!(civil_from_days(19_782), (2024, 2, 29));
}
#[test]
fn render_has_time_channel_and_colors() {
let stamp = Stamp {
year: 2026,
month: 9,
day: 24,
hour: 13,
minute: 45,
second: 12,
milli: 130,
};
assert_eq!(
render(Level::Error, Channel::Pipe, true, &stamp, "boom"),
"[\u{1b}[1m\u{1b}[31mERROR\u{1b}[0m 2026-09-24 13:45:12.130] [pipe] boom"
);
assert_eq!(
render(Level::Warn, Channel::Core, true, &stamp, "careful"),
"[\u{1b}[1m\u{1b}[38;5;208mWARN \u{1b}[0m 2026-09-24 13:45:12.130] [core] careful"
);
assert_eq!(
render(Level::Debug, Channel::Pipe, true, &stamp, "detail"),
"[\u{1b}[1mDEBUG\u{1b}[0m 2026-09-24 13:45:12.130] [pipe] detail"
);
assert_eq!(
render(Level::Info, Channel::Core, true, &stamp, "hello"),
"[INFO 2026-09-24 13:45:12.130] [core] hello"
);
}
#[test]
fn render_without_color_is_plain() {
let stamp = Stamp {
year: 2026,
month: 9,
day: 24,
hour: 1,
minute: 2,
second: 3,
milli: 4,
};
assert_eq!(
render(Level::Error, Channel::Core, false, &stamp, "boom"),
"[ERROR 2026-09-24 01:02:03.004] [core] boom"
);
}
}