正确做法是用 Directory.EnumerateFiles 配合 File.GetLastWriteTime 和 DateTime.UtcNow 筛选,Path.Combine 拼路径,File.Exists 校验,FileStream 尝试打开判断占用,再删除;避免用 CreationTime、DateTime.Now、字符串拼接路径。

用 Directory.GetFiles 筛选再删,别直接遍历整个目录树
直接 Directory.EnumerateFiles 配合 File.GetCreationTime 或 File.GetLastWriteTime 判断,看似简单,但容易漏掉子目录或误删——尤其日志文件夹常有 2024/06/15/ 这类嵌套结构。正确做法是先获取所有匹配路径,再统一处理。
- 用
searchOption: SearchOption.AllDirectories确保查全,但注意性能:深层嵌套 + 大量小文件时,GetFiles会一次性加载路径数组,内存压力比EnumerateFiles高;若文件超万,优先改用EnumerateFiles - 时间判断必须用
LastWriteTime,不是CreationTime:日志轮转后文件创建时间可能重置,而最后写入时间才反映真实更新点 - 删除前加
try/catch捕获UnauthorizedAccessException和IOException:日志文件可能正被其他进程(如 NLog、Serilog)写入,强行删会抛异常
DateTime.Now.AddDays(-30) 是错的,要用 DateTime.UtcNow
本地时间(DateTime.Now)在服务器跨时区或夏令时切换时会导致误删——比如服务器设为 UTC+8,但日志时间戳是 UTC 写入的,差值就是 8 小时。更糟的是,AddDays(-30) 不考虑闰秒和系统时钟漂移,长期运行可能偏移半天。
- 统一用
DateTime.UtcNow做基准:日志库(如 Microsoft.Extensions.Logging)默认用 UTC 记录时间戳,对比才可靠 - 计算阈值用
DateTime.UtcNow.AddDays(-30),但注意:如果日志名含日期(如app-20240501.log),应解析文件名而非读取文件时间属性——IO 更少,且避免 NTFS 时间精度问题(Windows 下LastWriteTime最小精度是 100ns,但实际更新可能延迟) - 别用
DateTime.Today:它返回 00:00:00,删的是“今天零点前”的文件,不是“过去 30 天内”
删除前务必检查文件是否被占用,File.Delete 不会等锁释放
File.Delete 遇到被占用的文件直接抛 IOException,不会重试或跳过。生产环境里,NLog 默认启用文件锁定(concurrentWrites="true" 时用独占锁),这时删就必然失败。
- 先尝试用
FileStream以FileAccess.Read和FileShare.None打开文件:能打开说明未被写入,可安全删除;打不开就跳过或记录告警 - 不要用
Thread.Sleep等锁释放——没用,锁由持有进程控制,你等不到 - 若必须删,改用
MoveTo移到临时目录再删:绕过锁限制,但需额外磁盘空间
路径拼接别用字符串连接,Path.Combine 是唯一安全方式
手动拼 logDir + "\" + fileName 在 Linux 容器或 .NET 6+ 的跨平台部署中必出错——Path.DirectorySeparatorChar 可能是 /,硬写 导致路径无效,File.Delete 静默失败(不报错,文件还在)。
- 所有路径构造必须走
Path.Combine(logRoot, "logs", "error"),哪怕只有两级 - 传给
Delete前用File.Exists校验一次:防止因路径错误导致“删了假文件”却以为成功 - 日志路径若来自配置(如 JSON 中的
"logPath": "./logs"),记得用Path.GetFullPath转成绝对路径再操作,否则相对路径在不同工作目录下行为不一致
真正麻烦的不是删文件,而是删的时候发现某个日志正在被 IIS 工作进程锁着,或者文件系统是 NFS,LastWriteTime 返回的是挂载客户端时间而非服务端时间——这种问题不会在开发机上暴露,得去生产环境翻日志才能看到。










