go 1.16+ 推荐用 filepath.walkdir 遍历目录识别过期文件,它避免重复 stat、支持提前终止和符号链接控制;过期判断应基于 time.now().add(-keepduration) 与 info.modtime().before(cutoff) 比较,慎用 sub 计算;删除优先用 os.remove,失败再按需降级;需加文件锁防并发、白名单校验关键路径,并对失败分类重试与告警。

用 filepath.WalkDir 遍历目录并识别过期文件
Go 1.16+ 推荐用 filepath.WalkDir 替代旧的 filepath.Walk,它支持提前终止、避免重复 stat、且更省内存。清理逻辑的核心是判断文件是否“过期”——通常依据 ModTime(),但要注意:Windows 下 FAT32 文件系统可能只保留 2 秒精度,NTFS 也存在时区/夏令时影响。
实操建议:
- 统一用
time.Now().Add(-keepDuration)计算截止时间点,再与info.ModTime().Before(cutoff)比较,别用Sub后比秒数,易受时区干扰 - 跳过符号链接(
info.IsDir() && info.Mode()&os.ModeSymlink != 0),防止误删目标目录 - 对大目录加
context.WithTimeout控制遍历总耗时,避免卡死
用 os.Remove 和 os.RemoveAll 的选择逻辑
os.Remove 只删单个文件或空目录;os.RemoveAll 递归删整个路径(含非空目录)。自动清理中,90% 场景应优先用 os.Remove —— 它失败时明确报 directory not empty,便于你决定是否降级为 RemoveAll,而不是无脑递归删掉整个子树。
常见错误现象:直接对日志目录调 os.RemoveAll(dir),结果把还在写入的 current.log 和子目录全删了,服务崩溃。
实操建议:
- 先尝试
os.Remove(path);若报remove <path>: directory not empty</path>,再检查该目录是否属于预设的“可清空归档目录”,否则跳过 - 删除前用
os.Stat(path)确认存在且类型匹配(避免删错 symlink 目标) - 对关键路径(如
/var/log/myapp)加白名单校验:strings.HasPrefix(path, allowPrefix)
如何安全触发清理:定时器 + 文件锁防重入
用 time.Ticker 做定时任务很常见,但没加锁会导致多个实例并发清理同一目录,轻则重复删文件,重则 RemoveAll 中途被另一个进程删掉父目录,引发 panic。
实操建议:
- 用
os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)获取排他锁,成功才执行清理;失败立即 return - 锁文件必须放在清理范围外(如
/tmp/myapp-cleaner.lock),不能放在待清理的/var/log/myapp/.lock里,否则会被顺手删掉 - 清理结束后主动
lockFile.Close()并os.Remove(lockPath),但需 defer 保证即使 panic 也不残留锁
清理失败时的日志与退避策略
自动清理不是“删不掉就算了”。比如权限不足(permission denied)、设备忙(device or resource busy)、NFS 临时挂载失效,都得记录并设计重试逻辑,否则日志盘会悄无声息爆满。
实操建议:
- 对
os.Remove失败的路径,按错误类型分类处理:os.IsPermission(err)→ 记 warn 并跳过;errors.Is(err, syscall.EBUSY)→ 加入重试队列,5 分钟后重试一次 - 每次清理完统计
deletedCount和failedPaths,输出结构化日志(如 JSON),方便接入 ELK 告警 - 首次失败不告警,连续 3 次失败才触发钉钉/邮件通知,避免 NFS 暂时抖动引发噪音
真正难的不是写 delete 循环,而是判断“这个文件现在能不能删”——它可能正被 tail -f 读着,可能被另一个进程 mmap 写着,也可能在只读文件系统上。所有清理策略必须默认假设“删之前先确认状态”,而不是依赖事后补救。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











