当前位置:首页 > 资讯列表 >C# 实现日志按月打包归档及源文件清理

C# 实现日志按月打包归档及源文件清理

发布时间:2026-08-07 16:23:51 分类:营销学堂

服务长期运行会持续生成大量 `.log` 日志文件,日积月累会快速占满磁盘空间。需要一套自动归档方案:按文件名内日期筛选日志,按月打包Zip压缩,压缩成功后删除原始日志文件。

本文基于 System.IO.Compression.ZipArchive 实现,支持增量更新Zip包,避免重复写入相同日志,包含完整异常捕获,适用于 WinForm、Windows服务、控制台程序。

⚠️ 注意:依赖 System.IO.Compression.FileSystem 程序集,.NET Framework 4.7项目需要手动添加引用。只处理文件名包含 yyyy‑MM‑dd 格式日期的log文件。

业务需求

  • 根据文件名内 yyyy‑MM‑dd 日期,筛选指定起止时间区间的日志文件
  • 日志文件按月份分组,每个月生成一个 yyyy.MM_logs.zip 归档包
  • Zip包已存在时使用Update模式,判断条目是否已存在,防止重复添加日志
  • 全部文件写入Zip完成之后,再删除原始log文件,防止中途异常丢失数据
  • 输出处理日志,覆盖无文件、正常处理、异常报错三种场景
  • 不处理当月正在写入的活跃日志,只归档上月及更早历史日志

重要原则:必须压缩全部完成后再删除原文件;禁止边压缩边删除,避免程序崩溃造成日志丢失。

所需命名空间

csharp using引用
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;

调用示例

起始时间2016‑01‑01,结束时间取上月最后一天,跳过当月正在写入的日志:

csharp 调用入口
string path = @"D:\Logs";
DateTime startDate = new DateTime(2016, 1, 1);
DateTime endDate = new DateTime(System.DateTime.Now.Year, System.DateTime.Now.Month, 1).AddDays(-1);

CompressAndDeleteLogFilesByMonth(path, startDate, endDate);

核心方法完整代码

按月份压缩归档日志,压缩成功后删除原始log文件:

csharp 日志归档方法
/// <summary>
/// 按月份压缩归档日志文件,压缩成功后删除原log文件
/// 根据文件名中 yyyy‑MM‑dd 日期筛选文件
/// </summary>
/// <param name="folderPath">日志文件夹路径</param>
/// <param name="startDate">筛选起始日期</param>
/// <param name="endDate">筛选结束日期</param>
static void CompressAndDeleteLogFilesByMonth(string folderPath, DateTime startDate, DateTime endDate)
{
    try
    {
        // 正则表达式用于匹配文件名中的日期
        Regex dateRegex = new Regex(@"\d{4}-\d{2}-\d{2}");

        // 获取指定日期范围内的所有 .log 文件
        var logFiles = Directory.GetFiles(folderPath, "*.log")
                                .Where(file =>
                                {
                                    string fileName = Path.GetFileName(file);
                                    Match match = dateRegex.Match(fileName);
                                    if (match.Success)
                                    {
                                        DateTime fileDate;
                                        if (DateTime.TryParse(match.Value, out fileDate))
                                        {
                                            return fileDate >= startDate && fileDate <= endDate;
                                        }
                                    }
                                    return false;
                                })
                                .ToList();

        if (logFiles.Count > 0)
        {
            // 按月份分组
            var groupedFiles = logFiles.GroupBy(file =>
            {
                string fileName = Path.GetFileName(file);
                Match match = dateRegex.Match(fileName);
                if (match.Success)
                {
                    DateTime fileDate;
                    if (DateTime.TryParse(match.Value, out fileDate))
                    {
                        return new DateTime(fileDate.Year, fileDate.Month, 1);
                    }
                }
                return DateTime.MinValue;
            });

            foreach (var group in groupedFiles)
            {
                if (group.Key != DateTime.MinValue)
                {
                    string zipFileName = Path.Combine(folderPath, $"{group.Key:yyyy.MM}_logs.zip");
                    bool zipExists = File.Exists(zipFileName);
                    ZipArchiveMode mode = zipExists ? ZipArchiveMode.Update : ZipArchiveMode.Create;

                    using (ZipArchive zip = ZipFile.Open(zipFileName, mode))
                    {
                        foreach (string logFile in group)
                        {
                            string entryName = Path.GetFileName(logFile);
                            bool needAdd = true;

                            if (zipExists)
                            {
                                var existEntry = zip.GetEntry(entryName);
                                if (existEntry != null)
                                {
                                    needAdd = false;
                                }
                            }

                            if (needAdd)
                            {
                                zip.CreateEntryFromFile(logFile, entryName);
                            }
                        }
                    }

                    // 删除已压缩日志
                    foreach (string logFile in group)
                    {
                        File.Delete(logFile);
                    }
                    LogBaseWay.WriteTxt("处理日志", $"成功压缩并删除 {group.Key:yyyy.MM} 在 {Path.GetFileName(folderPath)} 目录下的 .log 文件。");
                }
            }
        }
        else
        {
            LogBaseWay.WriteTxt("处理日志", $"未找到 {startDate:yyyy.MM.dd} 到 {endDate:yyyy.MM.dd} 在 {Path.GetFileName(folderPath)} 目录下的 .log 文件。");
        }
    }
    catch (Exception ex)
    {
        LogBaseWay.WriteTxt("处理日志", $"发生错误: {ex.Message}");
    }
}

坑点与注意事项

  • 程序集引用:.NET Framework 需要手动引用 System.IO.Compression.FileSystem,否则 ZipFile.Open 编译报错。
  • 文件名格式依赖:正则匹配 yyyy‑MM‑dd,文件名不带日期的log直接跳过,不会处理。
  • 并发风险:多线程/多进程同时操作同一个zip包,会报文件被占用异常,建议单线程定时任务执行。
  • 文件夹权限:运行账号需要文件夹读、写、删除权限,权限不足抛出IO异常。
  • 大日志文件:超大log压缩占用CPU与IO,定时任务避开业务高峰期。
  • 正在占用的日志:代码没有判断文件是否被占用,如果日志还在被写入,File.Delete会抛出异常。

⚠️ LogBaseWay.WriteTxt 为项目内部日志工具,使用时替换为自己项目日志输出方式。

可扩展优化方向

  • 增加归档zip包过期清理,自动删除N个月前的压缩包
  • 增加文件占用检测,跳过正在被程序打开写入的日志文件
  • 支持递归扫描子目录下的log文件
  • 增加zip包大小判断,超过阈值自动拆分压缩包
  • 返回处理结果对象,方便外部监控告警

小结

该归档逻辑适合 Windows服务、后台定时任务,解决日志堆积磁盘占满问题,关键点:

1 先压缩,后删除,保障日志数据安全
2 Zip Update模式,避免重复归档相同日志
3 跳过当月日志,不处理正在写入的活跃日志
4 完整异常捕获,处理过程全部留日志记录

可以封装成定时任务,定期执行,自动完成历史日志归档,防止磁盘被日志填满。