最快方案是freopen,但仅限C风格输出;C++项目须重载streambuf或替换cout.rdbuf()以避免日志丢失乱序,需处理缓冲、线程安全、异常及路径兼容性。

直接结论:用 freopen 最快,但只适用于 C 风格 printf/fprintf(stderr, ...);若项目已用 std::cout,必须重载 std::streambuf 或替换 std::cout.rdbuf(),否则日志会丢失或乱序。
为什么 freopen("log.txt", "a", stdout) 不够用
它确实能把 printf 输出导到文件,但对 std::cout 无效——C++ 流和 C 流默认是分离的。更麻烦的是:freopen 不带缓冲控制,"a" 模式每次写入都可能触发系统调用,高频日志下性能骤降;且无法区分 stdout 和 stderr 的目标文件。
-
freopen后std::cout仍输出到原终端,除非额外调用std::ios_base::sync_with_stdio(false)并接受行为不可控风险 - 多线程写同一文件时,
freopen+printf无内置锁,容易出现日志行撕裂(如两行内容交错写入) - 无法在运行时动态切换日志路径,
freopen是“一次性覆盖”,再次调用需先fclose原流,而stdout关闭后printf行为未定义
安全可靠的做法:替换 std::cout 的 rdbuf()
核心是继承 std::streambuf,重写 overflow() 和 sync(),把字符攒够再批量写入文件。这样既保持 std::cout 语法不变,又可控缓冲大小和落盘时机。
关键点:
- 缓冲区大小建议设为 4096 字节(一页内存),太小失去缓冲意义,太大增加延迟
- 必须在
sync()中调用fflush或fsync,否则程序崩溃时最后一批日志会丢失 - 构造时用
std::ofstream以std::ios::app | std::ios::out打开文件,避免多进程写冲突(OS 层保证O_APPEND原子性) - 替换前保存原始
rdbuf,便于临时切回控制台调试:auto old = std::cout.rdbuf(my_log_buf);
示例片段:
class FileBuf : public std::streambuf {
std::ofstream file_;
std::vector<char> buffer_;
public:
FileBuf(const char* path) : file_(path, std::ios::app | std::ios::out), buffer_(4096) {
this->setp(buffer_.data(), buffer_.data() + buffer_.size() - 1);
}
int_type overflow(int_type ch) override {
if (ch != EOF) {
*this->pptr() = static_cast<char>(ch);
this->pbump(1);
}
return sync() == 0 ? traits_type::not_eof(ch) : traits_type::eof();
}
int sync() override {
if (this->pbase() != this->>pptr()) {
file_.write(this->pbase(), this->pptr() - this->pbase());
file_.flush(); // 关键:强制刷盘
this->setp(buffer_.data(), buffer_.data() + buffer_.size() - 1);
}
return 0;
}
};
// 使用:
FileBuf log_buf("app.log");
std::cout.rdbuf(&log_buf);
</char></char>
别忽略线程安全和异常边界
std::cout 本身线程安全(C++11 起对单次 操作加锁),但你的自定义 streambuf 不自动继承该锁。如果多个线程共用同一个 std::cout 写日志,必须手动加互斥:
- 在
overflow()和sync()开头加std::lock_guard<:mutex>,锁粒度宁小勿大 - 避免在
streambuf构造函数里抛异常——std::ofstream构造失败时,file_.is_open()为 false,应立刻返回错误码而非让std::cout绑定一个失效对象 - 日志文件路径含中文或空格?用
std::filesystem::u8path(C++17)转 UTF-8,std::ofstream原生只接受窄字符串
最易被跳过的细节:缓冲区溢出时 overflow() 可能被反复调用,但你的 sync() 若因磁盘满失败,不能静默吞掉数据——至少要记录到备用 stderr 或丢弃并置标志位,否则日志系统就成黑盒了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











