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

Delphi VCL应用日志文件创建异常问题求助

问题排查与修复方案

问题根源分析

你遇到的日志异常问题,核心原因是SearchFiles函数的逻辑缺陷:

  1. 排序逻辑错误:Windows的FindFirst/FindNext按字典序返回文件,而非数字序号排序。例如BSSGLog 20231106_10.txt会排在BSSGLog 20231106_2.txt之前,导致SearchFiles最终返回的是字典序最后一个文件(即_9.txt),而非序号最大的文件。
  2. 文件计数逻辑错误:GetFilesCount统计所有匹配文件的总数(包括无后缀的原文件),导致生成的新文件序号远大于实际需要的序号。当加载_9.txt时,其行数已达1000行,每次都会触发创建新文件,直到序号到89时,字典序中_89.txt成为最后一个文件且未达行数限制,才恢复正常写入。

修复后的代码

替换SearchFiles为GetLatestLogFile

该函数专门查找当前日期下序号最大的日志文件:

function GetLatestLogFile(const LogDir, DateStr: string): string;
var
  SearchRec: TSearchRec;
  BaseName: string;
  CurrentNum: Integer;
  MaxNum: Integer;
  FileNumStr: string;
begin
  Result := '';
  BaseName := 'BSSGLog ' + DateStr;
  MaxNum := -1; // -1代表无后缀的原文件

  // 检查原文件是否存在
  if FileExists(LogDir + BaseName + '.txt') then
  begin
    Result := BaseName + '.txt';
  end;

  // 遍历所有带后缀的日志文件,找出序号最大的
  if FindFirst(LogDir + BaseName + '_*.txt', faAnyFile xor faDirectory, SearchRec) = 0 then
  begin
    try
      repeat
        // 提取文件名中的数字序号部分
        FileNumStr := Copy(SearchRec.Name, Length(BaseName) + 2, Length(SearchRec.Name) - Length(BaseName) - 5);
        if TryStrToInt(FileNumStr, CurrentNum) then
        begin
          if CurrentNum > MaxNum then
          begin
            MaxNum := CurrentNum;
            Result := SearchRec.Name;
          end;
        end;
      until FindNext(SearchRec) <> 0;
    finally
      FindClose(SearchRec);
    end;
  end;
end;

重构Log过程

修复文件选择与序号生成逻辑,避免依赖错误的搜索与计数函数:

procedure Log(const s: string);
var
  FileCont: TStringList;
  FileName, LogEntry, DateStr: String;
  fs: TFormatSettings;
  LogDir: string;
  BaseName: string;
  CurrentNum: Integer;
begin
  DeleteFilesOlderThan30Days;

  // 统一日志目录路径,用TPath.Combine避免手动拼接错误
  LogDir := TPath.Combine(TPath.GetHomePath(), 'test\Logs\');
  if not DirectoryExists(LogDir) then
    ForceDirectories(LogDir); // 一次性创建多级目录

  FileCont := TStringList.Create;
  try
    // 获取当前日期字符串(YYYYMMDD格式)
    fs := TFormatSettings.Create;
    fs.ShortDateFormat := 'YYYYMMDD';
    DateStr := DateToStr(Now, fs);
    BaseName := 'BSSGLog ' + DateStr;

    // 获取最新的日志文件
    FileName := GetLatestLogFile(LogDir, DateStr);
    if FileName = '' then
    begin
      // 无现有日志文件,创建原文件
      FileName := BaseName + '.txt';
    end
    else
    begin
      // 加载现有文件,检查行数是否达到上限
      FileCont.LoadFromFile(LogDir + FileName);
      if FileCont.Count >= 1000 then
      begin
        // 计算下一个文件序号
        if FileName = BaseName + '.txt' then
          CurrentNum := 1 // 原文件满了,第一个后缀文件为_1
        else
        begin
          // 提取当前文件的序号并加1
          CurrentNum := StrToInt(Copy(FileName, Length(BaseName) + 2, Length(FileName) - Length(BaseName) - 5));
          Inc(CurrentNum);
        end;
        FileName := BaseName + '_' + IntToStr(CurrentNum) + '.txt';
        FileCont.Clear;
      end;
    end;

    // 生成日志条目并写入
    fs.ShortDateFormat := 'DD.MM.YYYY. HH:mm:ss';
    LogEntry := DateTimeToStr(Now(), fs) + '- ' + s;
    FileCont.Add(LogEntry);
    FileCont.SaveToFile(LogDir + FileName);
  finally
    FreeAndNil(FileCont);
  end;
end;

日志功能优化建议

  • 避免全文件加载:用TFileStream追加写入,仅读取文件行数而非加载全部内容,提升性能(尤其当日志文件较大时)。
  • 多线程安全:如果应用涉及多线程,需给日志操作加临界区(TCriticalSection),防止文件写入冲突。
  • 文件名序号补零:将后缀改为_001、_002格式,使字典序与数字序一致,彻底避免排序问题。
  • 增加错误处理:捕获文件操作异常(如文件被占用、权限不足),避免程序崩溃,可降级写入临时日志或输出到控制台。
  • 按文件大小滚动:相比行数限制,按文件大小(如10MB)滚动日志更合理,避免单条日志过长导致文件提前滚动。
  • 缓存目录路径:将日志目录路径缓存为全局变量,避免每次日志都重复计算路径。

内容的提问来源于stack exchange,提问作者q1werty

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:54:59