using System; using System.IO; using log4net; namespace CncCollector.Jobs { /// /// 日志文件归档任务。 /// 每天凌晨将前一天的日志文件移动到按日期命名的子目录中。 /// 目录结构示例: /// logs\collector.log ← 当前在写的运行日志 /// logs\collector_error.log ← 当前在写的错误日志 /// logs\2026-05-07\collector.log /// logs\2026-05-07\collector_error.log /// logs\2026-05-06\collector.log /// logs\2026-05-06\collector_error.log /// 保留天数外的旧目录自动清理。 /// public class LogArchiveJob { private static readonly ILog _log = LogManager.GetLogger(typeof(LogArchiveJob)); /// /// 日志保留天数 /// private readonly int _retainDays; /// /// 日志根目录(相对于BaseDirectory) /// private readonly string _logsDir; public LogArchiveJob(int retainDays = 30) { _retainDays = retainDays; _logsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs"); } /// /// 执行归档:把当前日志文件复制到昨天的日期目录,然后截断当前文件。 /// 应在每天凌晨00:00左右调用。 /// public void Execute() { try { if (!Directory.Exists(_logsDir)) { return; } var yesterday = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd"); var archiveDir = Path.Combine(_logsDir, yesterday); // 归档每个日志文件 ArchiveLogFile("collector.log", archiveDir); ArchiveLogFile("collector_error.log", archiveDir); // 清理过期目录 CleanupOldDirectories(); _log.Info($"日志归档完成({yesterday})"); } catch (Exception ex) { _log.Error("日志归档失败", ex); } } /// /// 归档单个日志文件到日期目录 /// private void ArchiveLogFile(string fileName, string archiveDir) { var sourceFile = Path.Combine(_logsDir, fileName); if (!File.Exists(sourceFile)) { return; } var fileInfo = new FileInfo(sourceFile); // 文件为空或太新(最后写入在1小时内)则跳过 // 避免归档正在活跃写入的当天日志 if (fileInfo.Length == 0) { return; } // 创建日期目录 if (!Directory.Exists(archiveDir)) { Directory.CreateDirectory(archiveDir); } var destFile = Path.Combine(archiveDir, fileName); // 如果目标已存在,追加序号 if (File.Exists(destFile)) { var seq = 1; var baseName = Path.GetFileNameWithoutExtension(fileName); var ext = Path.GetExtension(fileName); do { destFile = Path.Combine(archiveDir, $"{baseName}_{seq}{ext}"); seq++; } while (File.Exists(destFile)); } // 复制到日期目录(不是移动,因为log4net还持有文件句柄) File.Copy(sourceFile, destFile); // 截断当前日志文件(清空内容,但不删除文件,保持log4net文件句柄有效) File.WriteAllText(sourceFile, string.Empty); _log.Info($"已归档 {fileName} → {archiveDir}({fileInfo.Length} 字节)"); } /// /// 清理超过保留天数的日期目录 /// private void CleanupOldDirectories() { if (!Directory.Exists(_logsDir)) { return; } var cutoff = DateTime.Now.AddDays(-_retainDays).ToString("yyyy-MM-dd"); foreach (var dir in Directory.GetDirectories(_logsDir)) { var dirName = Path.GetFileName(dir); // 日期目录格式:yyyy-MM-dd if (dirName.Length == 10 && dirName.Contains("-") && dirName.CompareTo(cutoff) < 0) { try { Directory.Delete(dir, true); _log.Info($"已清理过期日志目录:{dirName}"); } catch (Exception ex) { _log.Warn($"清理过期日志目录失败:{dirName}", ex); } } } } } }