不用fsnotify易漏事件,因其依赖inotify/kqueue缓冲队列,未及时消费则溢出丢事件;需持续读Events通道、避免阻塞操作、递归监听须手动实现,并用Rename事件或size稳定校验确保读取完整内容。

为什么不用 fsnotify 就容易漏事件
Linux/macOS 下文件系统事件(如创建、删除、重命名)不是“实时广播”,而是通过 inotify/kqueue 缓冲队列传递。如果监听逻辑没及时消费,缓冲区溢出就会丢事件——典型表现是 fsnotify 的 Event.Op 里突然缺了 fsnotify.Create,只剩 fsnotify.Write。这不是 bug,是机制限制。
解决办法不是调大缓冲区(sysctl fs.inotify.max_user_watches 只治标),而是:
- 用
fsnotify.Watcher启动后立即启动 goroutine 持续读取w.Events通道,不能等业务逻辑空闲时才读 - 对
fsnotify.Rename和fsnotify.Remove做路径缓存比对,因为重命名可能拆成两个事件(旧路径删 + 新路径建),需主动关联 - 避免在事件处理中做阻塞操作(如同步写日志、HTTP 请求),否则会拖慢通道消费速度
fsnotify 监听目录时必须递归自己实现
fsnotify 默认只监听指定目录的直接子项,不会自动进入子目录。想监控整个树形结构,得手动遍历 + 逐层 w.Add()。但要注意:filepath.WalkDir 遍历时若遇到权限拒绝(如 /proc/123/fd),会中断整个遍历。
稳妥做法:
- 用
filepath.WalkDir+ 自定义WalkFunc,遇到os.ErrPermission时不 return,而是continue - 每次
w.Add(dir)后检查返回错误,忽略fsnotify: no such file or directory(说明目录已被删,跳过即可) - 监听过程中新创建的子目录不会自动加入,需在收到
Create事件且IsDir()为 true 时,再调用一次w.Add()
如何判断文件是否真正写完(避免读到截断内容)
编辑器保存文件常分两步:先写临时文件(如 .foo.swp),再原子替换(rename)。但有些工具(如 echo "x" > file)直接覆盖写,触发 Write 事件时文件可能还在写入中。直接读会拿到不完整数据。
实用判断逻辑:
- 收到
Write事件后,用os.Stat()检查Size()是否稳定——连续两次间隔 100ms 获取 size 相同,大概率写完了 - 对文本类文件,可加简单校验:读最后 1KB,看是否以换行符结尾,或 JSON 是否能
json.Unmarshal成功(失败则等下次事件) - 完全规避风险的做法:只响应
Rename事件(临时文件 → 目标文件),此时替换已完成,内容必然完整
Windows 下 fsnotify 的路径大小写陷阱
Windows 文件系统默认不区分大小写,但 fsnotify 内部用的是底层 API(ReadDirectoryChangesW),它返回的路径大小写与实际磁盘存储一致。这意味着:你用 w.Add("C:\Logs"),但事件里 Event.Name 可能是 "c:\logs\app.log",字符串比较直接失效。
立即学习“go语言免费学习笔记(深入)”;
正确处理方式:
- 所有路径匹配前统一转小写:
strings.ToLower(event.Name) - 避免用
path/filepath.Join拼接监听路径和事件名,改用filepath.ToSlash标准化分隔符后再比较 - 测试时务必在真实 Windows 环境跑,CI 里用 WSL 会掩盖这个问题(WSL 的 ext4 是区分大小写的)
Write 是追加还是覆盖?Rename 是移动还是编辑器保存?这些没法靠库解决,得结合业务场景补规则。


















