c++oding="utf-8" ?>
最直接的方法是每次写日志时同时调用 std::cout 和 std::ofstream;多数场景下无需第三方库,c++ 标准库即可实现手动双写日志。

用 std::ofstream 和 std::cout 手动双写日志最直接
多数场景下不需要第三方库,C++ 标准库就能搞定。核心思路是:每次写日志时,同时调用 std::cout 和 <code>file_stream ,再手动加换行和刷新。
常见错误是忘记 std::endl 或 std::flush,导致文件里日志延迟出现甚至丢失;另一个坑是多个线程同时写,没加锁会乱序或截断。
- 打开文件时务必检查
file_stream.is_open(),否则静默失败 - 建议用
std::ios::app模式追加写入,避免每次覆盖 - 每条日志末尾统一用
std::endl(它自动 flush),别混用"\n" - 多线程环境下,对两个流的写入必须包裹在同一个
std::mutex临界区里
封装成简易日志类时注意 operator 的链式调用
想支持 logger 这种写法,就得重载 <code>operator 并返回引用。关键点在于:不能直接返回 <code>*this,因为要同时写两路输出,得把格式化过程延迟到语句结束。
典型做法是用一个临时缓冲对象(比如 LogStream)承接所有
- 不要在
operator 里立即输出,否则链式调用会分段刷到终端和文件,顺序错乱 -
LogStream析构函数里统一调用std::cout和file_stream,并加std::endl - 记得在析构中做
file_stream.flush(),尤其程序异常退出时更可靠 - 如果日志量大,频繁构造/析构临时对象有开销,可考虑用线程局部存储缓存
LogStream
用 std::streambuf 实现透明双路输出(高级但易出错)
这是最“干净”的方案:继承 std::streambuf,重写 overflow() 和 sputn(),把数据同时推给 std::cout.rdbuf() 和文件流的 rdbuf()。然后用这个自定义 streambuf 构造一个 std::ostream 对象。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
好处是上层代码完全无感,my_logger 就自动双写;坏处是 <code>streambuf 接口底层、调试困难,且不同 STL 实现(libstdc++ vs libc++)对 sputn 行为处理略有差异。
- 必须显式调用
pubsync()或确保overflow()返回值符合规范,否则部分字符可能丢弃 - 不能直接把
std::cout的rdbuf()再塞回去,会造成无限递归——要保存原始rdbuf并绕过它写 - 文件流的
rdbuf()在std::ofstream关闭后失效,需在streambuf中持有有效引用或指针 - Windows 下换行符 \r\n 可能被双重转换,建议在
overflow中统一处理为 \n
为什么不用 tee 命令或系统重定向
开发期用 ./app | tee app.log 看起来省事,但实际问题很多:程序崩溃时 tee 可能漏掉最后几行;stderr 不会被捕获;无法控制日志格式(比如加时间戳);Windows 没原生 tee;而且根本没法在代码里动态开关文件输出。
还有人试过 freopen("log.txt", "a", stdout),这只能重定向 stdout,无法同时保留控制台输出,且会干扰其他依赖 stdout 的库(如某些测试框架)。
-
freopen后std::cout和printf都写进文件,控制台就没了 - POSIX 的
dup2+pipe方案太重,还要 fork 子进程,不适用于嵌入式或实时性要求高的场景 - 跨平台项目里,硬依赖 shell 工具会让 CI 构建在 Windows 上失败
真正可靠的方案还是在 C++ 层面控制双路输出,哪怕只是几行封装,也比依赖外部命令更可控。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










