需手动实现日志轮转:按大小阈值(如10mb)检查,每n条日志用std::filesystem::file_size()检测一次,超限时原子关闭旧流并打开新文件;推荐序号命名(app.log.1)、扫描清理旧文件;注意windows句柄占用、大日志预判、信号重开(sighup)及多线程安全。

用 std::ofstream 写日志时,怎么自动切文件?
不能靠 std::ofstream 自己切——它没轮转逻辑。得手动判断当前文件大小,超限时关闭旧流、打开新文件。关键不是“怎么写”,而是“什么时候关、什么时候开”。
常见错误是每次写都 stat() 一遍:性能差,尤其高频日志;更糟的是在多线程里不加锁读写同一个 std::ofstream 对象,直接导致文件内容错乱或崩溃。
- 只在写入前检查一次大小(比如每 100 条日志 check 一次),避免频繁系统调用
- 用
std::filesystem::file_size()(C++17)比stat()更简洁,但注意 Windows 下可能有缓存延迟,别依赖实时精确值 - 切换文件必须原子:先
close()当前流,再用新文件名构造新std::ofstream,中间不能有日志丢失窗口
日志文件名带时间戳还是序号?选错会卡死清理
用时间戳(如 "app-20240520-143215.log")看起来直观,但轮转时容易撞名(同一秒多次启动)、排序难、清理逻辑复杂;用序号("app.log.1", "app.log.2")更可控,但必须维护一个计数器或扫描目录推导最大序号。
最容易被忽略的坑:Windows 下如果旧文件正被其他进程打开(比如你双击用记事本看了眼),std::filesystem::remove() 会失败,后续轮转可能卡住或静默丢日志。
- 推荐序号方案:轮转时把当前
app.log改名为app.log.1,已有的.1→.2,依此类推 - 清理旧文件时,用
std::filesystem::directory_iterator扫描匹配"app.log.*"的文件,按序号降序排,删掉超过保留数量(如 5 个)的 - 删除前加
std::this_thread::sleep_for(1ms)再试一次——有时文件句柄释放有毫秒级延迟
logrotate 能不能直接接管 C++ 程序的日志?
能,但程序得配合。如果 C++ 程序自己 open() 日志文件后一直持有 fd,logrotate 的 copytruncate 会清空文件内容,但你的程序还在往原 fd 写,结果日志“消失”了——看着文件没增长,实际内容进黑洞。
正确做法是让程序响应 SIGHUP:收到信号后 close() 当前日志 fd,再重新 open() 同名文件。这样 logrotate 就能干净切分,不用改程序内部逻辑。
- 必须在程序启动时用
signal(SIGUSR1, ...)或sigaction()注册信号处理器(Linux/macOS 常用SIGHUP或SIGUSR1) - 信号处理函数里只设标志位(
volatile sig_atomic_t need_reopen = 1),真正 close/open 放主循环里做,避免异步信号不安全函数 -
logrotate配置里加postrotate ... kill -USR1 `cat /var/run/myapp.pid` ... endscript
单次写入超大日志(比如 dump 二进制)会绕过大小限制
轮转逻辑通常基于“累计写入量”,但如果某次 write() 或 ofstream::write() 直接塞入 50MB 数据,而阈值设的是 10MB,那这一条就撑爆磁盘——检测机制完全失效。
这不是边界 case,是真实发生过的线上事故:调试时临时加了内存 dump,结果日志目录占满,服务假死。
- 在写入前做长度预判:
if (data.size() > max_file_size) { rotate_now(); } - 对超长日志拆成多段写,每段写完检查剩余空间,而不是一股脑
write() - 更稳妥的做法:把大日志单独输出到
"app-dump-XXXXX.bin"这类隔离路径,不走主日志流
file_size() 和 remove() 的行为会变得更不可预测。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











