logrotate配置比手写循环覆盖更可靠,C++进程只需追加写入,由logrotate通过copytruncate等指令安全轮转;纯C++实现环形覆盖仅适用于定长日志的嵌入式场景,且读取端需自解析有效性。

logrotate 配置比手写循环覆盖更可靠
直接在 C++ 里用 fopen + fseek 模拟“循环覆盖”日志,实际几乎不可行:你无法在固定文件大小内做结构化追加(比如每条日志带时间戳、长度不一),强行截断会破坏最后一条日志的完整性,且多进程/多线程写入时极易损坏数据。生产环境应交由 logrotate 管理,C++ 进程只需按常规方式追加写入 app.log,然后配置 logrotate 按大小轮转并保留 N 个旧文件。
典型 /etc/logrotate.d/myapp 配置示例:
/var/log/myapp/app.log {
size 10M
rotate 5
copytruncate
missingok
compress
}copytruncate 是关键:它先复制当前日志再清空原文件,避免 C++ 进程因文件被 mv 而丢失写入目标。
如果必须纯 C++ 实现固定大小覆盖,用 mmap + ring buffer 方式
仅适用于日志格式高度可控(如每条定长、无换行、无变长字段)的嵌入式或性能敏感场景。核心是用 mmap 将文件映射为内存,维护两个偏移:write_pos(当前写入点)、read_pos(最早有效日志起始)。当 write_pos 到达末尾,回绕到开头并覆盖旧内容。
立即学习“C++免费学习笔记(深入)”;
- 必须用
O_SYNC或定期msync,否则断电可能丢失最后几 KB - 需原子更新
write_pos,推荐用std::atomic<size_t>,避免多线程写入错位 - 读取端需自行解析环形区域,不能直接
fgets;要从read_pos开始扫描合法日志头(比如固定 magic bytes) - 文件创建时必须
ftruncate到目标大小,否则mmap失败或映射区域为空
避免用 freopen 或 fclose + fopen 实现“覆盖”
常见错误写法:freopen("app.log", "a", stdout) 配合定时重开——这不会真正覆盖,只是追加;而 fclose 后 fopen("app.log", "w") 会清空整个文件,导致正在写的日志丢失,且 POSIX 不保证 fopen("w") 原子性,可能留下零长文件。
更糟的是在 Windows 上用 _fsopen 加 _SH_DENYNO,仍无法解决多进程竞争。结论:不要试图用文件打开模式控制覆盖逻辑。
真正需要“覆盖”的本质,通常是日志保留策略误设
用户说“循环覆盖”,往往真实需求是“只保留最近 24 小时日志”或“磁盘空间不超过 1GB”。这时候正确路径是:
- 让日志框架(如
spdlog)启用rotating_logger_mt,设置max_file_size = 10 * 1024 * 1024和max_files = 100 - 若用
glog,通过FLAGS_max_log_size和FLAGS_stop_logging_if_full_disk控制 - 自研方案中,用独立看门狗线程定期检查
statvfs剩余空间,触发rename+unlink最老日志文件
环形文件真正的难点不在写,而在安全读——你怎么确认某段字节是完整、未被覆盖一半的日志?这个问题没有银弹,得结合你的日志协议设计(比如每条前 4 字节存 CRC,后 4 字节存长度)来验证有效性。


















