收集日志的时候,经常会使用 tail -f xxx.log
命令来实现,我看一本书 《Linux 内核分析及应用》的时候看到了 tail
使用了 inotify
来实现主动发现文件的变更。(相关问题:Python 如何检查文件是否发生变化)
在2.6内核之后,Linux提供了inotify功能,内核通过监控文件系统的变更来反向通知用户,这样减少了轮询的开销。我们来看其实现:
tail_forever_inotify (int wd, struct File_spec *f, size_t n_files, double sleep_interval) { .. f[i].wd = inotify_add_watch (wd, f[i].name, inotify_wd_mask); ... if (pid) { if (writer_is_dead) exit (EXIT_SUCCESS); writer_is_dead = (kill (pid, 0) ! = 0 && errno ! = EPERM); struct timeval delay; //等待文件变化的时间 if (writer_is_dead) delay.tv_sec = delay.tv_usec = 0; else { delay.tv_sec = (time_t) sleep_interval; delay.tv_usec = 1000000 * (sleep_interval - delay.tv_sec); } fd_set rfd; FD_ZERO (&rfd); FD_SET (wd, &rfd); int file_change = select (wd + 1, &rfd, NULL, NULL, &delay); if (file_change == 0) continue; else if (file_change == -1) die (EXIT_FAILURE, errno, _("error monitoring inotify event")); } ... len = safe_read (wd, evbuf, evlen); .
所以以上步骤主要分为三步:
1)注册inotify的watch。
2)用select等待watch事件发生。
3)用safe_read读取准备好的数据。
这是类似于 IO 多路复用的技术,但是 inotify
只能实现告知文件发生了变化,不能告知文件具体多了哪些字符。
所以我觉得 tail 要把新的内容打印出来,他需要通过记录文件的偏移量(一个记录举例文件开头共有几个字节指针,或者说变量),然后 inotify
告知有变化后,读取偏移量起始到末尾的所有字符。
问题:这看起来工作的很好,但是我突然在,如果变化发生在文件的中部会怎么样?
我发现如果我支持 > xxx.log
,把这个日志文件清空之后,tail -f 依然正常工作。
大概意思就是说,如果这个日志文件已经有 100 GB 了,tail -f 的偏移量应该也是在 100GB 的位置, 然后 > xxx.log
清空日志文件,但是 tail -f 的偏移量貌似就自动变成了 0
我想知道这是怎么实现的,去看了 tail 的源代码(http://git.savannah.gnu.org/c...),但是限于本人的 c 工程能力,表示看不懂🤡,也去看了 promtail 的实现(https://github.com/grafana/lo...),但是限于本人的 golang 工程能力,表示看不懂🤡
但是因为我在研究一个问题:loko、elk等日志收集工具如何处理日志的时间轮换问题,从而诞生了这个问题。
参考文章:
python中文件变化监控-watchd
Linux tail 命令源码解析 (这个文章是有问题的,🤡 文章中说 tail 是检查文件变化是根据时间戳而不是 inotify)
linux inotify 监控文件系统事件
确认一下你的问题:
验证环境
我们将使用tail -f 和 strace验证以上两种环境。测试环境如下:
问题1:覆盖写
验证覆盖写情况
准备工作
创建测试文件a.txt
tail -f该文件
在其他终端中,strace tail进程
追加写入"1234"
tail -f 输出如下
strace输出如下
覆盖写
echo "qwer" > a.txt
tail输出为
strace输出为:
由输出可知,tail -f 执行了以下逻辑
该部分注释也说明了,文件被truncated时,fspec->size会被置为0,同时调用lseek将读取位置设置到文件头。这样就做到了正常输出。
问题2:文件中部变化
接下来测试一下文件中部变化的实际情况,继续在问题1的基础上操作。
strace 无新增输出,可见调用了select之后再未进行系统调用
查看tail进程文件描述符发现,a.txt已被删除。尚不清除该情况是否因vi引起。
总结,问题2无法给出原因,仅能给出一种特定环境下的执行结果。