
用 std::cout 或 std::cerr 打印日志,够用但不推荐长期用
直接输出到控制台看似简单,但实际项目里很快会遇到问题:没时间戳、不能分级(INFO/WARN/ERROR)、无法重定向到文件、多线程下输出错乱。尤其在调试 Release 版本或服务端进程时,std::cout 的缓冲行为会导致日志延迟甚至丢失。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 仅限临时调试,且必须加
std::flush或换行符避免缓冲滞留:std::cerr - 避免在信号处理函数、析构函数中使用
std::cout—— 它不是异步信号安全的 - 别把
std::cout和printf混用,C++ 流和 C 标准库流的缓冲策略不同,可能引发竞态
自己写一个线程安全的轻量级日志类,核心就三件事
不需要功能齐全,只要能解决“多线程写、带级别、可开关、不拖慢主逻辑”这四个痛点,50 行内就能搞定。关键不是封装多漂亮,而是避开锁竞争和内存分配。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
std::atomic<bool></bool>控制日志开关,避免每次判断都走分支预测失败 - 日志级别用
enum class LogLevel { ERROR, WARN, INFO };,编译期过滤:用if constexpr (level >= ENABLED_LEVEL)让低级别日志彻底不生成代码 - 避免在日志函数里做格式化(如
std::to_string、std::stringstream),改用fmt::format_to或 C++20 的std::format_to配合栈上 buffer,减少堆分配 - 输出前用
std::lock_guard<:mutex></:mutex>包住write()调用,但只锁写文件/终端这一步,别把格式化也包进去
spdlog 是最稳妥的“开箱即用”选择,但要注意默认配置的坑
它快、跨平台、支持异步、有现成的 daily rotating file sink,但新手常栽在两个地方:初始化时机不对,和忘记设置 flush on crash。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 必须在
main()开头就调用spdlog::set_default_logger(),否则静态变量初始化顺序可能导致 segfault - 异步 logger 默认不保证崩溃时日志落盘,要手动加:
logger->flush_on(spdlog::level::err); - Windows 下用
spdlog::stdout_color_mt()会触发控制台句柄复用问题,建议改用spdlog::stderr_color_mt() - 如果程序启动极快(比如 CLI 工具),加一句
spdlog::shutdown();在退出前,防止异步队列未清空就进程退出
日志路径和权限问题,在 Linux 服务场景下最容易被忽略
本地跑得好好的,一丢进 systemd 就写不了日志文件——不是代码问题,是运行环境没配对。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 绝对路径优先,避免依赖当前工作目录;用
std::filesystem::absolute()做一次归一化 - Linux daemon 启动时工作目录通常是
/,没有写权限,别硬写"./logs/app.log" - systemd 服务要显式声明
RuntimeDirectory=logs和RuntimeDirectoryMode=0755,再把日志路径设为/run/myapp/logs/ - 文件打开用
std::ios::app | std::ios::out,别用std::ios::trunc—— 重启服务不该清空历史日志
真正难的不是写日志,是让日志在各种部署形态下稳定出现。从容器里 exec -it 进去看不到日志,大概率不是代码漏了 flush,而是日志路径被挂载覆盖或权限锁死了。









