Delphi VCL应用日志文件创建异常问题求助
问题排查与修复方案
问题根源分析
你遇到的日志异常问题,核心原因是SearchFiles函数的逻辑缺陷:
- 排序逻辑错误:Windows的
FindFirst/FindNext按字典序返回文件,而非数字序号排序。例如BSSGLog 20231106_10.txt会排在BSSGLog 20231106_2.txt之前,导致SearchFiles最终返回的是字典序最后一个文件(即_9.txt),而非序号最大的文件。 - 文件计数逻辑错误:
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
相关产品推荐
相关产品推荐

