.NET Web API线程安全日志器文件占用与循环日志问题
解决.NET Web API循环日志文件无法删除的问题
核心问题出在静态构造中初始化的TextWriter长期持有文件句柄,导致日志轮转时无法释放文件锁。以下是几种可行的解决方案,兼顾线程安全和日志轮转需求:
方案一:锁内动态管理TextWriter生命周期
通过静态锁保证线程安全,在每次日志写入前检查文件大小,需要轮转时手动释放旧的TextWriter,完成备份后再创建新的Writer:
public static class ThreadSafeLogger { private static readonly object _logSyncLock = new object(); private static TextWriter _activeLogWriter; private static readonly string _logPath = "app.log"; private static readonly long _maxLogSize = 1024 * 1024; // 1MB阈值 public static void Write(string logMessage) { lock (_logSyncLock) { EnsureLogFileValid(); _activeLogWriter.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | {logMessage}"); _activeLogWriter.Flush(); // 强制写入磁盘,避免缓存丢失 } } private static void EnsureLogFileValid() { // 首次初始化Writer if (_activeLogWriter == null) { _activeLogWriter = new StreamWriter(_logPath, true, Encoding.UTF8); return; } // 检查是否达到轮转阈值 var logFileInfo = new FileInfo(_logPath); if (logFileInfo.Length < _maxLogSize) return; // 释放旧Writer,释放文件句柄 _activeLogWriter.Close(); _activeLogWriter.Dispose(); // 执行备份(添加时间戳避免覆盖) var backupFileName = $"Backup_{DateTime.Now:yyyyMMddHHmmss}_{Path.GetFileName(_logPath)}"; if (File.Exists(backupFileName)) File.Delete(backupFileName); File.Move(_logPath, backupFileName); // 创建新的日志Writer _activeLogWriter = new StreamWriter(_logPath, true, Encoding.UTF8); } }
关键说明:
- 所有文件操作都在
lock块内执行,彻底避免Parallel.ForEach中的竞态问题。 - 轮转时手动调用
Close()和Dispose()释放文件句柄,解决文件占用问题。 - 每次写入后调用
Flush(),确保日志即时写入磁盘,防止轮转时丢失缓存数据。
方案二:使用File.AppendAllText配合锁(简单但性能稍差)
如果日志写入频率不高,可以直接用File.AppendAllText,配合静态锁保证线程安全,不需要长期持有Writer:
public static class ThreadSafeLogger { private static readonly object _logSyncLock = new object(); private static readonly string _logPath = "app.log"; private static readonly long _maxLogSize = 1024 * 1024; public static void Write(string logMessage) { lock (_logSyncLock) { CheckLogRotation(); var logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | {logMessage}{Environment.NewLine}"; File.AppendAllText(_logPath, logEntry, Encoding.UTF8); } } private static void CheckLogRotation() { var logFileInfo = new FileInfo(_logPath); if (logFileInfo.Length < _maxLogSize) return; var backupFileName = $"Backup_{DateTime.Now:yyyyMMddHHmmss}_{Path.GetFileName(_logPath)}"; if (File.Exists(backupFileName)) File.Delete(backupFileName); File.Move(_logPath, backupFileName); } }
优缺点:
- 优点:无需管理Writer生命周期,代码更简洁。
- 缺点:每次写入都要打开/关闭文件句柄,高频率写入时性能不如方案一。
额外注意事项
- 备份文件建议添加时间戳,避免重复备份覆盖旧文件。
- 可以添加异常捕获逻辑,处理文件被意外占用的情况(比如其他进程临时访问日志文件)。
- 对于.NET Core/.NET 5+,也可以考虑使用官方的
Microsoft.Extensions.Logging框架,配合文件日志提供器(如Serilog、NLog),这些框架已内置线程安全的日志轮转功能,无需手动实现。
内容的提问来源于stack exchange,提问作者SavindraSingh
相关产品推荐
相关产品推荐

