直接监听文件夹路径可行,但须确保目录存在且有读权限,手动遍历子目录注册监听,同时处理原子写入(优先响应Rename事件)、过滤临时文件,并用带缓冲channel持续消费Events/Errors以避免阻塞。

直接监听文件夹路径是可行的,但必须确保路径存在、有读权限,且要手动处理子目录、临时文件和事件类型误判——否则大概率收不到变更或收到一堆脏事件。
watcher.Add() 传入路径必须是真实存在的目录
传 watcher.Add("logs/") 看似合理,但如果 logs/ 不存在或权限不足,Add() 在 Linux/macOS 下可能静默失败(返回 nil 错误),Windows 下更可能 panic。这不是库的 bug,而是底层系统调用的限制。
- 监听前先
os.Stat("logs/"),检查err == nil && info.IsDir() - 若路径是符号链接,用
filepath.EvalSymlinks("logs/")解析真实路径再监听,否则可能监听到链接自身而非目标目录 - 不要监听空字符串、
.或..—— 各平台行为不一致,Linux 可能成功但 macOS 会报错
fsnotify 不支持递归监听,必须手动遍历子目录
watcher.Add("configs/") 只监听该目录自身的元数据变化(比如被重命名),对 configs/db/config.yaml 的修改完全无感。没有 recursive: true 这种参数。
- 用
filepath.WalkDir("configs/", ...)遍历所有子目录,对每个os.DirEntry调用watcher.Add(entry.Name()) - 跳过符号链接(除非你明确想监控链接文件本身):检查
entry.Type() & os.ModeSymlink != 0 - 监听大量子目录时注意系统 inotify 限制:
/proc/sys/fs/inotify/max_user_watches(Linux),超限会报too many open files
编辑器保存后收不到 Write 事件?大概率是原子写入 + 事件过滤太粗
Vim、VS Code、Sublime 等默认启用原子写入:先写 config.yaml~ 或 .config.yaml.tmp,再 rename 覆盖原文件。这导致原 inode 失效,Write 事件根本不会触发在目标文件上。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 必须同时监听
fsnotify.Write和fsnotify.Rename,且优先响应后者:if event.Op&fsnotify.Rename != 0 && filepath.Base(event.Name) == "config.yaml" - 过滤临时文件:用
strings.HasSuffix(event.Name, "~")、strings.HasSuffix(event.Name, ".swp")、strings.HasSuffix(event.Name, ".tmp") - 收到事件后立刻
os.Stat(event.Name),确认文件存在、大小非零,避免处理残留的临时文件
Events 和 Errors channel 必须持续消费,否则 watcher 会卡死
watcher.Events 和 watcher.Errors 都是无缓冲 channel。如果没人读,往里写事件就会阻塞整个 watcher —— 新事件进不来,旧事件也发不出,监听彻底失效。
- 必须启动独立 goroutine 消费两个 channel,不能只读 Events 而忽略 Errors
- 推荐用
select+default非阻塞读取,或加缓冲(如make(chan fsnotify.Event, 100)),但缓冲区大小需结合业务吞吐预估 -
watcher.Close()后,仍要读完 Events 和 Errors 中最多各 1 个残留项,否则可能 panic:send on closed channel
真正难的不是注册监听,而是区分“编辑器正在保存”和“保存已完成”,以及在不同平台下对 Chmod、Rename、Create 的语义做一致判断——这些细节不处理,热重载就容易加载一半配置或 panic。

















