做完rust全链路

This commit is contained in:
LIm1tZz7 committed 2026-09-24 21:28:49 +08:00
1 parent e428265357
commit 2a4b446252
34 files changed
+1756 -1031

No files matched your search

+36 -40
View File
@@ -3,23 +3,21 @@ use std::io::{IsTerminal, Write};
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::OnceLock;
// 极简日志:等级存在一个原子变量里,被关掉的等级只花一次原子读——不求值参数、不做格式化。
// 行格式:[等级 本地日期时间] [来源] 消息
// - ERROR 加粗红、WARN 加粗橙、DEBUG 加粗不变色(只在 stderr 是终端时着色,重定向到文件时是纯文本)
// - 来源区分 core(服务自身:启动、配置)和 pipe(由管道请求触发)
// 等级由配置文件的 log.level 或环境变量 CHUSQL_LOG 决定。
// 极简日志:等级用原子变量,关掉的等级只花一次原子读。
/// 转义序列收尾。
// 颜色
/// 转义序列收尾
const RESET: &str = "\u{1b}[0m";
/// 加粗红(ERROR)。
/// 加粗红(ERROR)
const BOLD_RED: &str = "\u{1b}[1m\u{1b}[31m";
/// 加粗橙(WARN):256 色里的 208 号。
/// 加粗橙(WARN)
const BOLD_ORANGE: &str = "\u{1b}[1m\u{1b}[38;5;208m";
/// 只加粗(DEBUG)。
/// 只加粗(DEBUG)
const BOLD: &str = "\u{1b}[1m";
/// 日志等级;Off 表示全关。
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
// 等级与来源
/// 日志等级
pub enum Level {
Off,
Error,
@@ -28,15 +26,15 @@ pub enum Level {
Debug,
}
/// 日志来源:core = 服务自身,pipe = 由管道请求触发。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// 来源:服务自身或管道请求
pub enum Channel {
Core,
Pipe,
}
impl Channel {
/// 日志行里的来源标签。
/// 来源标签
fn tag(self) -> &'static str {
match self {
Channel::Core => "core",
@@ -46,7 +44,7 @@ impl Channel {
}
impl Level {
/// 解析等级名(大小写不敏感)。
/// 解析等级名
pub fn parse(s: &str) -> Option<Level> {
match s.trim().to_ascii_lowercase().as_str() {
"off" => Some(Level::Off),
@@ -58,7 +56,7 @@ impl Level {
}
}
/// 等级名(写日志和写配置说明用)。
/// 等级名
pub fn name(self) -> &'static str {
match self {
Level::Off => "off",
@@ -69,7 +67,7 @@ impl Level {
}
}
/// 日志行里的标签,固定 5 字符宽,方便对齐。
/// 行里的等级标签
fn tag(self) -> &'static str {
match self {
Level::Error => "ERROR",
@@ -79,7 +77,7 @@ impl Level {
}
}
/// 这个等级要不要着色(INFO 不着色)。
/// 等级的颜色
fn color(self) -> &'static str {
match self {
Level::Error => BOLD_RED,
@@ -90,29 +88,28 @@ impl Level {
}
}
/// 当前等级,默认 info。
static LEVEL: AtomicU8 = AtomicU8::new(Level::Info as u8);
/// stderr 是不是终端(决定要不要着色),只问一次。
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)
}
/// 启动时调一次:Windows 上给 stderr 打开 VT 处理,让 ANSI 颜色能被认识。
/// 启动时调一次
pub fn init() {
enable_vt();
}
/// 写出一行;只在 enabled 为真时调用。
/// 写出一行
pub fn write(channel: Channel, level: Level, args: fmt::Arguments) {
let msg = args.to_string();
let line = render(level, channel, color_enabled(), &now(), &msg);
@@ -121,7 +118,7 @@ pub fn write(channel: Channel, level: Level, args: fmt::Arguments) {
let _ = writeln!(out, "{}", line);
}
/// stderr 是终端才着色;CHUSQL_LOG_COLOR=always / never 可以强制开关。
/// 是否着色(终端或强制)
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,
@@ -130,7 +127,7 @@ fn color_enabled() -> bool {
})
}
/// 拼一行:[等级 时间] [来源] 消息。纯函数,方便测试。
/// 拼一行(纯函数,可测)
fn render(level: Level, channel: Channel, color: bool, stamp: &Stamp, msg: &str) -> String {
let tag = level.tag();
let paint = level.color();
@@ -142,8 +139,9 @@ fn render(level: Level, channel: Channel, color: bool, stamp: &Stamp, msg: &str)
format!("[{} {}] [{}] {}", level_text, stamp, channel.tag(), msg)
}
/// 一个本地日期时间。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
// 时间
/// 本地日期时间
pub struct Stamp {
pub year: u16,
pub month: u16,
@@ -154,6 +152,7 @@ pub struct Stamp {
pub milli: u16,
}
/// 格式:年-月-日 时:分:秒.毫秒
impl fmt::Display for Stamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
@@ -164,7 +163,7 @@ impl fmt::Display for Stamp {
}
}
/// 当前本地时间:Windows 上走 kernel32::GetLocalTime。
/// 当前本地时间(Windows)
#[cfg(windows)]
fn now() -> Stamp {
#[repr(C)]
@@ -206,7 +205,7 @@ fn now() -> Stamp {
}
}
/// 当前时间:其他平台没有取本地时区的零依赖办法,退回 UTC。
/// 当前时间(其他平台用 UTC)
#[cfg(not(windows))]
fn now() -> Stamp {
let d = std::time::SystemTime::now()
@@ -225,7 +224,7 @@ fn now() -> Stamp {
}
}
/// 把"距 1970-01-01 的天数"换算成 (年, 月, 日):非 Windows 平台算时间用,单元测试也走这里。
/// 天数换算成日期
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;
@@ -239,7 +238,8 @@ pub fn civil_from_days(days: i64) -> (i64, u16, u16) {
(y + i64::from(month <= 2), month, day)
}
/// Windows:打开 stderr 的 VT 处理。
// 终端
/// 打开 Windows 的 VT 处理
#[cfg(windows)]
fn enable_vt() {
#[link(name = "kernel32")]
@@ -249,9 +249,7 @@ fn enable_vt() {
fn SetConsoleMode(handle: *mut core::ffi::c_void, mode: u32) -> i32;
}
/// STD_ERROR_HANDLE:-12 的无符号写法。
const STD_ERROR_HANDLE: u32 = 0xFFFF_FFF4;
/// 让控制台认识 ANSI 转义序列。
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
unsafe {
@@ -263,11 +261,12 @@ fn enable_vt() {
}
}
/// 非 Windows:不用做任何事。
/// 其他平台什么都不用做
#[cfg(not(windows))]
fn enable_vt() {}
/// 打一条 debug 日志(关掉时只花一次原子读)。
// 宏
/// debug 日志
#[macro_export]
macro_rules! log_debug {
(core, $($arg:tt)*) => {
@@ -278,7 +277,7 @@ macro_rules! log_debug {
};
}
/// 打一条 info 日志。
/// info 日志
#[macro_export]
macro_rules! log_info {
(core, $($arg:tt)*) => {
@@ -289,7 +288,7 @@ macro_rules! log_info {
};
}
/// 打一条 warn 日志。
/// warn 日志
#[macro_export]
macro_rules! log_warn {
(core, $($arg:tt)*) => {
@@ -300,7 +299,7 @@ macro_rules! log_warn {
};
}
/// 打一条 error 日志。
/// error 日志
#[macro_export]
macro_rules! log_error {
(core, $($arg:tt)*) => {
@@ -311,7 +310,7 @@ macro_rules! log_error {
};
}
/// 上面四个宏的公共部分:先看等级,再看要不要真的写。内部用,别直接调。
/// 四个宏的公共部分
#[doc(hidden)]
#[macro_export]
macro_rules! log_line {
@@ -326,7 +325,6 @@ macro_rules! log_line {
mod tests {
use super::*;
/// 天数换算日期:几个已知点 + 两次闰年的 2 月 29 日。
#[test]
fn civil_from_days_known_dates() {
assert_eq!(civil_from_days(0), (1970, 1, 1));
@@ -336,7 +334,6 @@ mod tests {
assert_eq!(civil_from_days(19_782), (2024, 2, 29));
}
/// 行格式:等级 + 本地日期时间 + 来源 + 消息,等级该粗的粗、该色的色。
#[test]
fn render_has_time_channel_and_colors() {
let stamp = Stamp {
@@ -367,7 +364,6 @@ mod tests {
);
}
/// 关掉颜色时整行必须是纯文本(重定向到文件时用)。
#[test]
fn render_without_color_is_plain() {
let stamp = Stamp {