用线性扫描+状态机解析日志格式串更高效,遇%读取后续字符判断字段,按非字母数字或结尾界定字段边界,区分大小写,预处理__file__为相对路径。

用 std::regex 解析格式字符串太重,别这么干
多数人一想“解析”,本能掏 std::regex,但 C++11 的正则在编译器支持和运行时开销上都很不友好——GCC 4.9–5.x 的 std::regex 实现甚至有严重 bug,Clang 也默认禁用;更关键的是,日志格式串是固定模式(%t、%m、%f:%l 这类),用正则纯属杀鸡用炮。实际只需一次线性扫描 + 状态机跳转。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 遍历字符串每个字符,遇到
%就看下一个字符:是字母就匹配预定义字段(t→时间,m→消息,f→文件名),是%就输出一个字面% - 跳过空格、制表符等无关字符,不依赖分隔符——用户写
%t [%l]%m和%t[%l]%m都应正常工作 - 避免把整个格式串拆成 vector
再拼接,每次日志输出都 new/delete 会拖慢性能
支持 %f:%l 这种复合字段的关键是「字段结束判定」
像 %f 单独出现没问题,但 %f:%l 要求解析器知道 %f 的边界在哪——不是靠冒号,而是靠「非字母数字字符」或字符串尾。否则 %file 会被错当成 %f + ile,输出文件名后多出 “ile”。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 读到
%后,取紧邻的下一个字符c;若c是字母,则检查c后是否为非字母数字(如:、、)或结尾),是则认作有效字段 - 预留扩展位:比如
%f{20}表示截取前 20 字符,此时需识别{开始的数字段,但先做基础版,别一上来就搞 DSL - 字段名区分大小写:
%M和%m视为不同,避免和系统宏(如_GNU_SOURCE)冲突
Windows 下 __FILE__ 返回绝对路径,但日志里要相对路径
直接插 __FILE__ 会导致日志刷满盘符(C:projsrclog.cpp),既难读又暴露构建路径。Linux 虽无盘符问题,但长路径同样干扰排查。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 编译时用
-D__FILENAME__="$(notdir $(Make)或 <code>/D__FILENAME__="$(notdir $(MSBuildThisFile))"(MSVC)定义简短文件名宏,替代__FILE__ - 若必须用
__FILE__,在解析阶段做一次std::string_view截取:从最后一个/或\后取子串,不用std::string::find_last_of避免构造临时对象 - 注意 MSVC 的
__FUNCSIG__比__FUNCTION__更全,但体积大,日志里用后者更稳
va_list 传递消息体时,vsnprintf 的缓冲区大小容易溢出
用 vsprintf 直接写栈缓冲区?危险。用 vsnprintf(nullptr, 0, ...) 先测长度?对某些旧 libc(如 uClibc)不兼容。最稳妥的是两段式:先用小缓冲(256 字节)尝试,失败再动态分配。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 声明栈上
char buf[512],调用vsnprintf(buf, sizeof(buf), fmt, args);若返回值 ≥sizeof(buf),说明不够,再 malloc 对应大小重试 - 不要用
std::vformat(C++20)替代——它不支持va_list,且 GCC 12 前实现有性能坑 - 消息体本身不含 % 符号?那也要过一遍格式化函数,否则用户传
"%s"进来却没给参数,vsnprintf会 UB
字段解析看着简单,但路径处理、缓冲区安全、跨平台宏展开这三块,漏一个就导致日志乱码或崩溃。别信“用个第三方库就行”,轻量日志的控制权必须握在自己手里。










