商城首页欢迎来到中国正版软件门户

您的位置:首页 >C++日志系统宏定义实现代码

C++日志系统宏定义实现代码

  发布于2026-03-10 阅读(0)

扫一扫,手机访问

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

C++ 怎么打印日志 C++简单日志系统宏定义实现代码【工程】

std::cout 和宏组合实现轻量日志输出

直接用 std::cout 打日志太裸,没时间戳、没文件名行号、关起来麻烦。最常用做法是封装一层宏,编译期控制开关,运行时不拖慢主逻辑。

关键点在于:宏要能自动注入 __FILE____LINE____func__,还要支持像 printf 那样可变参数。C++11 起推荐用 variadic macro + std::ostringstreamfmt::format(若引入第三方),但纯标准库下更稳妥的是用 std::vfprintf 配合 stderr 或文件句柄。

  • 宏定义里必须用 do { ... } while(0) 包裹,避免 if/else 分支下误展开
  • 不要在宏里直接调用 std::cout << ... << std::endl —— 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。不过这要求每个文件手动加,易漏。

  • 推荐折中方案:用 std::string_view(__FILE__).substr(...).data() 在运行时截取,只在 debug 日志启用(不影响 release 性能)
  • 更简单的 hack:#define SHORT_FILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__),然后在日志宏里用 SHORT_FILE
  • 注意:strrchr 是 C 标准函数,需包含 <cstring>,且不是 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 可能被重定向或行缓冲,日志卡住不输出

日志宏看着简单,但文件名截取、线程安全、编译开关和参数求值时机这四点,任何一个没压住,上线后都可能变成隐性性能瓶颈或排查盲区。

本文转载于:互联网 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注