You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 10:20:25