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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:36:20