直接监听目录并过滤create事件可行,但必须处理原子写入(优先响应rename)、过滤临时文件(如~/.tmp后缀)、递归监听子目录(手动walkdir+动态add)及持续消费events/errors通道,否则vs code/vim保存时必然失灵。

直接监听目录并过滤 Create 事件是可行的,但必须处理原子写入、临时文件干扰和子目录遗漏——否则你写的“只响应新建”逻辑大概率在 VS Code 或 vim 保存时完全失灵。
监听目录前必须确保路径存在且可读
传给 watcher.Add() 的路径不能是空字符串、. 或 ..,各平台行为不一致;更关键的是,如果目录不存在或权限不足,Linux/macOS 下 Add() 可能静默返回 nil 错误,导致你以为监听成功,实际什么也收不到。
- 监听前务必
os.Stat(dir),检查err == nil && info.IsDir() - 若路径是符号链接,先用
filepath.EvalSymlinks(dir)解析真实路径再监听 - 避免监听根目录或深层嵌套路径(macOS kqueue 对 >100 层目录可能触发
fsnotify.ErrEventOverflow)
如何正确捕获“真正的新建文件”事件
编辑器保存文件时普遍采用原子写入:先创建临时文件(如 config.yaml~ 或 .config.yaml.tmp),再 Rename 覆盖原文件。这意味着你看到的不是 Create,而是 Rename + Remove 组合事件。只判断 e.Op == fsnotify.Create 会漏掉全部真实变更。
- 用位运算判断:
if e.Op&fsnotify.Create != 0,而非== - 同时监听
fsnotify.Rename,并对目标文件名做后缀过滤:strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".tmp") - 收到疑似新建事件后,立刻
os.Stat(e.Name)确认文件存在且大小非零,避免处理残留临时文件
子目录里的新建文件怎么监听到
fsnotify 默认不递归,watcher.Add("logs/") 只监听 logs/ 目录自身的元数据变化(比如被 mv 或 chmod),对 logs/app/error.log 的 Create 事件完全无感。
- 启动时用
filepath.WalkDir("logs/", ...)遍历所有子目录,对每个entry.Type().IsDir()的路径调用watcher.Add(entry.Name()) - 监听到
fsnotify.Create且info.IsDir()为true时,主动watcher.Add(e.Name)新增子目录监听 - 注意系统限制:Linux 的
/proc/sys/fs/inotify/max_user_watches有上限,监听大量子目录时可能报too many open files
Events 和 Errors channel 不消费就会卡死
watcher.Events 和 watcher.Errors 是无缓冲 channel。一旦阻塞,后续所有事件都会积压甚至丢失——这不是偶发 bug,是设计使然。
- 必须用 goroutine 持续读取两个 channel,不能只读一个或加了
select却没设默认分支 - 不要在 handler 里做耗时操作(如网络请求、磁盘写入),否则会拖慢整个事件流;建议把
e.Name推进带缓冲的 worker channel 异步处理 - 程序退出前务必调用
watcher.Close(),否则 fd 泄露,多次重启后可能触发系统级限制
最易被忽略的点:没有原子写入兜底逻辑的“新建监听”,在真实编辑器场景下等于没写;而递归监听若不处理新增子目录的动态注册,就只是个半成品。这两处不补全,监控功能在生产环境必然失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











