应使用带 do{...}while(0) 包裹的可变参数宏,自动注入__FILE__、__LINE__、__func__,通过std::vfprintf配合stderr实现编译期开关的日志输出,避免直接调用std::cout。

用 std::cout 和宏组合实现轻量日志输出
直接用 std::cout 打日志太裸,没时间戳、没文件名行号、关起来麻烦。最常用做法是封装一层宏,编译期控制开关,运行时不拖慢主逻辑。
关键点在于:宏要能自动注入 __FILE__、__LINE__、__func__,还要支持像 printf 那样可变参数。C++11 起推荐用 variadic macro + std::ostringstream 或 fmt::format(若引入第三方),但纯标准库下更稳妥的是用 std::vfprintf 配合 stderr 或文件句柄。
- 宏定义里必须用
do { ... } while(0)包裹,避免 if/else 分支下误展开 - 不要在宏里直接调用
std::cout ——std::endl强制 flush,高频日志会严重拖慢性能 - 用
\n替代std::endl,必要时单独 flush - 示例基础宏:
#define LOG_INFO(fmt, ...) \
do { \
fprintf(stderr, "[%s:%d %s] " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
} while(0)
__FILE__ 路径太长?用宏截取文件名部分
默认 __FILE__ 展开为绝对路径(如 /home/user/project/src/log.cpp),日志里塞满冗余路径,既难读又占空间。得在宏里做字符串裁剪 —— 但 C 预处理器不支持字符串操作,所以得靠编译器扩展或运行时处理。
常见解法是用 GCC/Clang 的 __builtin_strrchr 或手动写个 basename 风格函数,但更轻量的是利用预处理器拼接:定义一个包装宏,在源文件开头用 #define THIS_FILE "log.cpp",再在日志宏里用 THIS_FILE。不过这要求每个文件手动加,易漏。
立即学习“C++免费学习笔记(深入)”;
- 推荐折中方案:用
std::string_view(__FILE__).substr(...).data()在运行时截取,只在 debug 日志启用(不影响 release 性能) - 更简单的 hack:
#define SHORT_FILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__),然后在日志宏里用SHORT_FILE - 注意:
strrchr是 C 标准函数,需包含,且不是 constexpr,不能用于 static_assert
如何关闭日志而不删代码?用 NDEBUG 或自定义开关宏
线上环境通常完全禁用调试日志,但又不能把所有 LOG_DEBUG 行注释掉。最干净的方式是依赖编译宏控制是否展开。
- 用
#ifdef DEBUG_LOG包裹整条日志语句,未定义时宏展开为空 —— 无任何运行时开销 - 不要用
if (g_log_enabled) { LOG(...) },即使 flag 为 false,参数表达式仍会求值(比如LOG("val=%d", expensive_func())会白调一次) - 进阶:按等级分层,例如
LOG_LEVEL设为 0~3,宏内部用if constexpr(C++17)或模板特化跳过低优先级日志的展开 - 示例开关宏:
#ifdef ENABLE_LOG
# define LOG_DEBUG(fmt, ...) LOG_INFO(fmt, ##__VA_ARGS__)
#else
# define LOG_DEBUG(fmt, ...) do {} while(0)
#endif多线程环境下 fprintf 安全吗?要不要加锁?
fprintf(stderr, ...) 在多数 libc 实现中是线程安全的(内部有锁),但不保证原子性 —— 两行日志内容可能交叉(如 A 线程打一半,B 线程插进来,最后输出乱序)。对调试够用,但审计或追踪类日志不行。
真正需要顺序一致,就得自己加锁,或改用线程局部缓冲 + 定期刷出。不过锁本身有开销,尤其高并发场景。
- 简单项目:用
std::mutex全局锁 +std::lock_guard,锁粒度控制在单次fwrite内 - 更高效:用
__thread(GCC)或thread_local缓冲区,每个线程攒几条再批量写,减少系统调用 - 注意:
std::cout默认也是线程安全的,但同样不保证跨行原子性;且它比fprintf多一层 streambuf 抽象,略慢 - 别用
printf替代fprintf(stderr, ...)——stdout可能被重定向或行缓冲,日志卡住不输出
日志宏看着简单,但文件名截取、线程安全、编译开关和参数求值时机这四点,任何一个没压住,上线后都可能变成隐性性能瓶颈或排查盲区。











