C#处理大日志文件写入CSV时报文件被其他进程占用错误
问题现象
我编写了一个功能函数,用于读取文件夹内所有.log格式日志文件,提取其中的错误行后写入.csv文件。该功能处理小体积日志文件时运行正常,但处理大于600KB的大文件时会返回错误:
The process cannot access the file because it is being used by another process.
所有日志文件都统一存放在logs文件夹中,功能实现代码如下:
/* * Extract data from the log file and right it in a conf.csv file */ public void DataExtract(string path) { int index = 0; int nextLine = 0; int descriptionLine = 0; string firstLine = ""; string secondLine = ""; string description = ""; try { if (!String.IsNullOrEmpty(path)) { // Read each line of the file into a string array. Each element // of the array is one line of the file. string[] logs = System.IO.File.ReadAllLines(path); string[] confFile = System.IO.File.ReadAllLines(this.confPath); // read each line of the log file foreach (string log in logs) { if (log.Contains("ERROR")) { nextLine = index + 1; descriptionLine = index + 2; firstLine = log; secondLine = logs[nextLine]; string checkDescr = ""; int descNb = descriptionLine + 1; checkDescr = logs[descNb]; description = logs[descriptionLine]; if (!description.Contains("at")) { descriptionLine++; description = logs[descriptionLine]; } if (!confFile.Any(s => s.Contains(firstLine)) || !confFile.Any(s => s.Contains(secondLine))) { using (StreamWriter sw = File.AppendText(this.confPath)) { sw.WriteLine(string.Format("{0},{1},{2}", firstLine, secondLine, description)); } } index++; } Console.WriteLine("Done"); } } catch (Exception e) { Console.WriteLine("Problem !"); Console.WriteLine(e.Message); } } }
主类中的调用逻辑如下:
string logPath = directoryPath + "\\logs"; string[] logfiles = Directory.GetFiles(logPath, "*.log"); ErrorRecover errorRecover = new ErrorRecover(); // For each log file call the methode for extracting errors logs foreach (var file in logfiles) { Console.WriteLine(file); errorRecover.DataExtract(file); }
问题原因
- 代码存在严重的IO冗余:每次遍历到含ERROR的日志行,都会重复读取全量conf.csv内容、重复打开关闭conf.csv写入流。大文件处理时匹配到的ERROR行更多,文件句柄频繁创建释放,锁释放存在延迟时就会触发占用错误。
- 默认文件打开模式的锁策略冲突:
File.ReadAllLines、File.AppendText默认使用写独占锁,大文件读取耗时更长,和高频写入操作撞锁的概率远高于小文件场景。如果日志文件同时被其他程序(比如正在写日志的服务)占用,默认打开模式会直接抛出占用异常。 - 索引逻辑错误:
index变量仅在匹配到ERROR行时才自增,遍历普通行时索引不更新,会导致读取后续关联行时索引错位,大文件行数多时错位概率极高,容易触发意外的访问异常。 - 去重逻辑失效:每次写入新内容后没有更新内存里的
confFile缓存,同一个错误重复写入时会重复打开文件,进一步放大IO冲突概率。
修复方案
- 调整IO操作位置:所有日志文件处理前仅读取一次conf.csv内容存入内存做去重校验,整个处理流程仅打开一次conf.csv写入流,处理完成后统一释放,避免频繁开关文件。
- 读取日志文件时显式指定共享模式:使用
File.Open搭配FileShare.ReadWrite参数打开日志文件,允许其他进程同时读写日志,不会因为日志正在被写入就抛出占用错误。 - 修正索引逻辑:每遍历一行日志,无论是否匹配ERROR,index都要自增,同时增加边界判断,避免索引越界。
- 内存缓存实时更新:每次写入新的错误记录后,同步更新内存中的去重缓存,避免重复写入。
修复后的核心代码参考:
public void ProcessAllLogs(string logFolder, string confPath) { // 启动时仅读一次配置文件做去重 HashSet<string> existedRecords = new HashSet<string>(); if (File.Exists(confPath)) { foreach (var line in File.ReadAllLines(confPath)) { existedRecords.Add(line); } } // 整个处理流程只打开一次写入流 using (StreamWriter sw = new StreamWriter(confPath, true, Encoding.UTF8)) { string[] logfiles = Directory.GetFiles(logFolder, "*.log"); foreach (var file in logfiles) { Console.WriteLine(file); // 显式指定共享模式打开日志,允许其他进程同时读写 using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (StreamReader sr = new StreamReader(fs)) { List<string> logs = new List<string>(); while (!sr.EndOfStream) { logs.Add(sr.ReadLine()); } string[] logArr = logs.ToArray(); // 逐行遍历,索引自动递增 for (int index = 0; index < logArr.Length; index++) { string log = logArr[index]; if (log.Contains("ERROR")) { // 边界判断,避免索引越界 if (index + 3 >= logArr.Length) break; string firstLine = log; string secondLine = logArr[index + 1]; string description = logArr[index + 2]; if (!description.Contains("at")) { description = logArr[index + 3]; } string record = string.Format("{0},{1},{2}", firstLine, secondLine, description); if (!existedRecords.Contains(record)) { sw.WriteLine(record); existedRecords.Add(record); } } } } Console.WriteLine("Done"); } } }
内容的提问来源于stack exchange,提问作者feperc
相关产品推荐
相关产品推荐

