Files
ChuSQL/chusql-storage/src/log.rs
T

383 lines
10 KiB
Rust
Raw Normal View History

use std::fmt;
use std::io::{IsTerminal, Write};
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::OnceLock;
2026-09-24 21:28:49 +08:00
// 极简日志:等级用原子变量,关掉的等级只花一次原子读。
2026-09-24 21:28:49 +08:00
// 颜色
/// 转义序列收尾
const RESET: &str = "\u{1b}[0m";
2026-09-24 21:28:49 +08:00
/// 加粗红(ERROR)
const BOLD_RED: &str = "\u{1b}[1m\u{1b}[31m";
2026-09-24 21:28:49 +08:00
/// 加粗橙(WARN)
const BOLD_ORANGE: &str = "\u{1b}[1m\u{1b}[38;5;208m";
2026-09-24 21:28:49 +08:00
/// 只加粗(DEBUG)
const BOLD: &str = "\u{1b}[1m";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2026-09-24 21:28:49 +08:00
// 等级与来源
/// 日志等级
pub enum Level {
Off,
Error,
Warn,
Info,
Debug,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2026-09-24 21:28:49 +08:00
/// 来源:服务自身或管道请求
pub enum Channel {
Core,
Pipe,
}
impl Channel {
2026-09-24 21:28:49 +08:00
/// 来源标签
fn tag(self) -> &'static str {
match self {
Channel::Core => "core",
Channel::Pipe => "pipe",
}
}
}
impl Level {
2026-09-24 21:28:49 +08:00
/// 解析等级名
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,
}
}
2026-09-24 21:28:49 +08:00
/// 等级名
pub fn name(self) -> &'static str {
match self {
Level::Off => "off",
Level::Error => "error",
Level::Warn => "warn",
Level::Info => "info",
Level::Debug => "debug",
}
}
2026-09-24 21:28:49 +08:00
/// 行里的等级标签
fn tag(self) -> &'static str {
match self {
Level::Error => "ERROR",
Level::Warn => "WARN ",
Level::Info => "INFO ",
_ => "DEBUG",
}
}
2026-09-24 21:28:49 +08:00
/// 等级的颜色
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();
2026-09-24 21:28:49 +08:00
// 写入
/// 设置全局等级
pub fn set_level(level: Level) {
LEVEL.store(level as u8, Ordering::Relaxed);
}
#[inline]
2026-09-24 21:28:49 +08:00
/// 这个等级要不要输出
pub fn enabled(level: Level) -> bool {
level as u8 <= LEVEL.load(Ordering::Relaxed)
}
2026-09-24 21:28:49 +08:00
/// 启动时调一次
pub fn init() {
enable_vt();
}
2026-09-24 21:28:49 +08:00
/// 写出一行
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);
}
2026-09-24 21:28:49 +08:00
/// 是否着色(终端或强制)
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(),
})
}
2026-09-24 21:28:49 +08:00
/// 拼一行(纯函数,可测)
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)]
2026-09-24 21:28:49 +08:00
// 时间
/// 本地日期时间
pub struct Stamp {
pub year: u16,
pub month: u16,
pub day: u16,
pub hour: u16,
pub minute: u16,
pub second: u16,
pub milli: u16,
}
2026-09-24 21:28:49 +08:00
/// 格式:年-月-日 时:分:秒.毫秒
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
)
}
}
2026-09-24 21:28:49 +08:00
/// 当前本地时间(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,
}
}
2026-09-24 21:28:49 +08:00
/// 当前时间(其他平台用 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,
}
}
2026-09-24 21:28:49 +08:00
/// 天数换算成日期
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)
}
2026-09-24 21:28:49 +08:00
// 终端
/// 打开 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);
}
}
}
2026-09-24 21:28:49 +08:00
/// 其他平台什么都不用做
#[cfg(not(windows))]
fn enable_vt() {}
2026-09-24 21:28:49 +08:00
// 宏
/// 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)*)
};
}
2026-09-24 21:28:49 +08:00
/// 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)*)
};
}
2026-09-24 21:28:49 +08:00
/// 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)*)
};
}
2026-09-24 21:28:49 +08:00
/// 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)*)
};
}
2026-09-24 21:28:49 +08:00
/// 四个宏的公共部分
#[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"
);
}
}