应结合stat检查文件大小变化与增量偏移管理读取追加日志:每次读前stat获st_size,对比上次偏移只读新增字节;轮转时st_size缩小则重置偏移为0;linux用inotify监听但需stat补差值,windows用readdirectorychangesw并注意共享模式与缓冲区大小。

用 fopen + fseek 持续读取追加写入的日志文件,会卡住或跳过新内容
日志文件被其他进程持续 fwrite 或 fprintf 追加时,C++ 程序若用传统方式打开后反复 fseek(fp, 0, SEEK_END) 再读,大概率读不到最新行——因为文件系统缓存、POSIX 文件偏移未同步、或写入方没刷缓冲区(fflush)。
关键不是“怎么打开”,而是“怎么感知变化并安全读取”。必须结合 stat 检查文件大小变化 + 增量偏移管理,不能依赖 feof 或固定循环次数。
- 每次读前先
stat获取当前st_size,和上次记录的偏移比较;只读新增字节,不重读也不跳过 - 读完立即更新偏移值,哪怕只读到半行(比如最后一行没换行符),也要保留该位置,下次从这里继续
- 遇到
st_size缩小(日志轮转),要重置偏移为 0 并重新fseek(fp, 0, SEEK_SET),否则会读错乱数据
Linux 下用 inotify 替代轮询,避免空耗 CPU 但要注意事件丢失
inotify 能监听 IN_MODIFY 或 IN_MOVED_TO(轮转场景),比每秒 stat 高效得多,但默认事件队列只有 16384 字节,高频写入时容易丢事件。
真正要用起来,得主动处理边界情况:
- 监听前先
stat一次获取初始大小,作为起始偏移;否则inotify只通知“变了”,不知道变了多少 - 收到
IN_MODIFY后,仍需stat查大小差值,再从记录偏移处读——inotify不告诉改了哪几字节 - 如果监听句柄失效(
read返回 -1 且errno == EINVAL),说明队列溢出,必须重建监听并回退到全量stat轮询逻辑兜底
Windows 上用 CreateFile + ReadDirectoryChangesW 读日志,路径权限和共享模式很关键
Windows 没有等价于 inotify 的轻量机制,ReadDirectoryChangesW 是唯一可行方案,但它监听的是目录,不是单个文件,而且对目标文件的打开方式极其敏感。
常见失败原因都卡在打开参数上:
- 日志写入方用
CREATE_ALWAYS或没设FILE_SHARE_READ,你的程序用CreateFile就会返回INVALID_HANDLE_VALUE - 必须用
FILE_FLAG_BACKUP_SEMANTICS才能对文件夹调用ReadDirectoryChangesW,否则直接失败 - 事件缓冲区太小(
lpBuffer小于 1KB)会导致ERROR_NOTIFY_ENUM_DIR,建议至少分配 8KB
收到变更通知后,仍要靠 GetFileSize 对比偏移读新内容,和 Linux 逻辑一致——通知只是触发信号,不携带增量信息。
跨平台封装时,std::ifstream 的 seekg 和 peek 容易误判 EOF
有人试图用 std::ifstream 配合 seekg 和 peek 实现流式读,结果发现 peek() 返回 EOF 后,即使文件已增长,后续 read 仍失败。这不是 bug,是 C++ 标准库流状态位没清。
必须手动干预状态机:
- 每次
peek()返回std::char_traits<char>::eof()</char>后,立刻调用file.clear()清除failbit和eofbit -
seekg到新位置后,不能直接>>或getline,要先file.peek()确认可读,否则可能因底层缓冲未刷新而阻塞 - 不要用
while (file >> ...),它依赖格式化提取,遇到空格/换行就停;应改用std::getline(file, line)配合file.fail()判断是否真出错
最麻烦的其实是编码:日志可能是 UTF-8、GBK 或无 BOM 的 ANSI,std::ifstream 默认按 locale 解码,读中文会乱码。除非明确知道日志编码,否则优先用 std::fstream 以二进制模式读,自己处理换行和解码。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











