用 spdlog。它头文件即用、无编译依赖、api 直观;log4cpp 需链接、xml 配置、初始化繁琐,新手易遇空指针、文件打开失败、段错误等问题,且 category::getroot() 非线程安全。

log4cpp 和 spdlog 哪个更适合新手快速上手
直接说结论:用 spdlog。它头文件即用、无编译依赖、API 直观,而 log4cpp 需要链接、配置 XML、初始化繁琐,新手容易卡在 log4cpp::BasicConfigurator::configure() 报空指针或找不到 appender。
常见错误现象:std::runtime_error: Unable to open file xxx.log(权限/路径问题)、Segmentation fault(log4cpp 全局 logger 未初始化就调用)。
实操建议:
- 用
spdlog就一行头文件:#include <spdlog></spdlog>,CMake 中加find_package(spdlog REQUIRED)或直接把include/spdlog拷进项目 -
log4cpp的Category::getRoot()不是线程安全的,多线程下必须手动加锁;spdlog默认线程安全,spdlog::info()可直接并发调用 - 如果项目已用 CMake 且支持 C++17,优先选
spdlog::stdout_color_mt("console")—— 颜色 + 多线程 + 无额外依赖
如何让日志自动带文件名、行号和函数名
靠宏,不是靠运行时反射。C++ 没有内置函数名获取,必须用 __FILE__、__LINE__、__FUNCTION__ 这类预定义宏拼接。
立即学习“C++免费学习笔记(深入)”;
容易踩的坑:spdlog::info("msg") 本身不带位置信息;自己写宏时忘记用 ##__VA_ARGS__ 支持变参,导致 LOG_INFO("x = {}", x) 编译失败。
实操建议:
- 定义一个宏:
#define LOG_INFO(fmt, ...) spdlog::info("[{}:{}] {}", __FILE__, __LINE__, fmt, ##__VA_ARGS__) - 更推荐用
spdlog::source_loc(v1.10+):spdlog::info(spdlog::source_loc{__FILE__, __LINE__, __FUNCTION__}, "msg"),输出格式统一且可被 sink 解析 - 注意
__FUNCTION__是 GCC/Clang 扩展,MSVC 用__FUNCSIG__,跨平台项目建议封装一层判断
DEBUG 级别日志只在调试构建中输出,怎么控制
不能靠运行时 if (level == DEBUG) 判断——那会拖慢 Release 性能,且字符串拼接仍会发生。得让编译器直接剔除整条语句。
性能影响:即使你写了 if (spdlog::level::debug == logger->level()) spdlog::debug("slow_to_string({})", obj),obj 的 operator 或 <code>to_string() 仍会被调用,开销白费。
实操建议:
- 用宏包裹整个日志调用:
#ifdef NDEBUG时定义LOG_DEBUG为空操作 - 示例:
#define LOG_DEBUG(fmt, ...) do { if (!NDEBUG) spdlog::debug(fmt, ##__VA_ARGS__); } while(0) - 更稳妥的是用
spdlog::debug自带的编译期开关:spdlog::set_level(spdlog::level::debug)只在 Debug 构建中调用,Release 中设为info或更高,再配合spdlog::cfg::load_from_file("log.conf")动态加载(但注意:文件 IO 本身有开销)
日志滚动到固定大小后自动归档,为什么总是卡住或丢日志
根本原因:滚动是 I/O 操作,同步模式下会阻塞主线程;异步模式下若没正确 shutdown,程序退出时日志队列里的内容直接丢失。
常见错误现象:xxx.log.1 文件存在但为空、xxx.log 一直不增长、程序崩溃前最后几条日志没写入磁盘。
实操建议:
- 用
spdlog::rotating_logger_mt而非basic_logger_mt,明确指定max_file_size和max_files - 必须在 main() 结束前调用
spdlog::shutdown(),否则 async logger 的 worker thread 可能来不及刷盘 - 避免在 signal handler(如 SIGINT)里直接调用
spdlog::info()—— 异步 logger 的内部队列不是 async-signal-safe 的,改用write(2)写 raw log 或提前注册spdlog::flush_on(spdlog::level::info)
复杂点在于:滚动策略和 flush 行为耦合紧密,但很多人只配了 size 却忘了调 shutdown,结果以为是库 bug。










