几年前,我编写了一个循环缓冲区二进制文件跟踪日志系统,它避免了文件无限增长的问题,同时为我提供了我想要的功能,例如,如果我愿意,能够看到问题,否则,能够忽略跟踪缓冲区。
但是,如果您想要一个连续的在线系统,那么我根本不会使用文件。
我使用文件是因为我确实想要类似文件的持久性并且不需要运行侦听器应用程序。我只是想要文件解决方案,因为我希望无论现在是否有人在“听”,我都希望记录发生,但没有使用无休止增长的文本日志,因为我担心在日志文件上用完数百兆,并填满我 250 兆字节的硬盘。在 1 TB 硬盘的时代,人们几乎没有这样的担忧。
正如大卫所说,客户端服务器解决方案是最好的,而且并不复杂。
但是你可能更喜欢文件,就像我一样,在我的情况下。我只是将我的查看器应用程序作为崩溃后运行的事后分析工具启动。这是在 MadExcept 或类似的东西出现之前,所以我有一些应用程序刚刚死掉,我想知道发生了什么。
在我的循环缓冲区之前,我会使用像 sys-internals DebugView 和 OutputDebugString 这样的调试视图工具,但是在我启动 DebugView 之前发生崩溃时,这对我没有帮助。
基于文件的日志记录(二进制)是我允许自己创建二进制文件的少数几次之一。我通常讨厌讨厌讨厌二进制文件。但是您只是尝试制作一个循环缓冲区而不使用固定长度的二进制记录。
这是一个示例单元。如果我现在而不是在 1997 年写这篇文章,我就不会使用“记录文件”,但是,嘿,就是这样。
要扩展此单元以便将其用作实时查看器,我建议您只需检查二进制文件上的日期时间戳并每 1-5 秒刷新一次(您的选择),但仅当二进制跟踪上的日期时间戳文件已更改。不难,也不会对系统造成很大的负担。
该单元用于记录器和查看器,它是一个可以读取和写入磁盘上的循环缓冲区二进制文件的类。
unit trace;
{$Q-}
{$I-}
interface
uses Classes;
const
traceBinMsgLength = 255; // binary record message length
traceEOFMARKER = $FFFFFFFF;
type
TTraceRec = record
index: Cardinal;
tickcount: Cardinal;
msg: array[0..traceBinMsgLength] of AnsiChar;
end;
PTraceBinRecord = ^TTraceRec;
TTraceFileOfRecord = file of TTraceRec;
TTraceBinFile = class
FFilename: string;
FFileMode: Integer;
FTraceFileInfo: string;
FStorageSize: Integer;
FLastIndex: Integer;
FHeaderRec: TTraceRec;
FFileRec: TTraceRec;
FAutoIncrementValue: Cardinal;
FBinaryFileOpen: Boolean;
FBinaryFile: TTraceFileOfRecord;
FAddTraceMessageWhenClosing: Boolean;
public
procedure InitializeFile;
procedure CloseFile;
procedure Trace(msg: string);
procedure OpenFile;
procedure LoadTrace(traceStrs: TStrings);
constructor Create;
destructor Destroy; override;
property Filename: string read FFilename write FFilename;
property TraceFileInfo: string read FTraceFileInfo write FTraceFileInfo;
// Default 1000 rows.
// change storageSize to the size you want your circular file to be before
// you create and write it. Remember to set the value to the same number before
// trying to read it back, or you'll have trouble.
property StorageSize: Integer read FStorageSize write FStorageSize;
property AddTraceMessageWhenClosing: Boolean
read FAddTraceMessageWhenClosing write FAddTraceMessageWhenClosing;
end;
implementation
uses SysUtils;
procedure SetMsg(pRec: PTraceBinRecord; msg: ansistring);
var
n: Integer;
begin
n := length(msg);
if (n >= traceBinMsgLength) then
begin
msg := Copy(msg, 1, traceBinMsgLength);
n := traceBinMsgLength;
end;
StrCopy({Dest} pRec^.msg, {Source} PAnsiChar(msg));
pRec^.msg[n] := Chr(0); // ensure nul char termination
end;
function IsBlank(var aRec: TTraceRec): Boolean;
begin
Result := (aRec.msg[0] = Chr(0));
end;
procedure TTraceBinFile.CloseFile;
begin
if FBinaryFileOpen then
begin
if FAddTraceMessageWhenClosing then
begin
Trace('*END*');
end;
System.CloseFile(FBinaryFile);
FBinaryFileOpen := False;
end;
end;
constructor TTraceBinFile.Create;
begin
FLastIndex := 0; // lastIndex=0 means blank file.
FStorageSize := 1000; // default.
end;
destructor TTraceBinFile.Destroy;
begin
CloseFile;
inherited;
end;
procedure TTraceBinFile.InitializeFile;
var
eofRec: TTraceRec;
t: Integer;
begin
Assert(FStorageSize > 0);
Assert(Length(FFilename) > 0);
Assign(FBinaryFile, Filename);
FFileMode := fmOpenReadWrite;
Rewrite(FBinaryFile);
FBinaryFileOpen := True;
FillChar(FHeaderRec, sizeof(TTraceRec), 0);
FillChar(FFileRec, sizeof(TTraceRec), 0);
FillChar(EofRec, sizeof(TTraceRec), 0);
FLastIndex := 0;
FHeaderRec.index := FLastIndex;
FHeaderRec.tickcount := storageSize;
SetMsg(@FHeaderRec, FTraceFileInfo);
Write(FBinaryFile, FHeaderRec);
for t := 1 to storageSize do
begin
Write(FBinaryFile, FFileRec);
end;
SetMsg(@eofRec, 'EOF');
eofRec.index := traceEOFMARKER;
Write(FBinaryFile, eofRec);
end;
procedure TTraceBinFile.Trace(msg: string);
// Write a trace message in circular file.
begin
if (not FBinaryFileOpen) then
exit;
if (FFileMode = fmOpenRead) then
exit; // not open for writing!
Inc(FLastIndex);
if (FLastIndex > FStorageSize) then
FLastIndex := 1; // wrap around to 1 not zero! Very important!
Seek(FBinaryFile, 0);
FHeaderRec.index := FLastIndex;
Write(FBinaryFile, FHeaderRec);
FillChar(FFileRec, sizeof(TTraceRec), 0);
Seek(FBinaryFile, FLastIndex);
Inc(FAutoIncrementValue);
if FAutoIncrementValue = 0 then
FAutoIncrementValue := 1;
FFileRec.index := FAutoIncrementValue;
SetMsg(@FFileRec, msg);
Write(FBinaryFile, FFileRec);
end;
procedure TTraceBinFile.OpenFile;
begin
if FBinaryFileOpen then
begin
System.CloseFile(FBinaryFile);
FBinaryFileOpen := False;
end;
if FileExists(FFilename) then
begin
// System.FileMode :=fmOpenRead;
FFileMode := fmOpenRead;
AssignFile(FBinaryFile, FFilename);
System.Reset(FBinaryFile); // open in current mode
System.Seek(FBinaryFile, 0);
Read(FBinaryFile, FHeaderRec);
FLastIndex := FHeaderRec.index;
FTraceFileInfo := string(FHeaderRec.Msg);
FBinaryFileOpen := True;
end
else
begin
InitializeFile; // Creates the file.
end;
end;
procedure TTraceBinFile.LoadTrace(traceStrs: TStrings);
var
ReadAtIndex: Integer;
Safety: Integer;
procedure NextReadIndex;
begin
Inc(ReadAtIndex);
if (ReadAtIndex > FStorageSize) then
ReadAtIndex := 1; // wrap around to 1 not zero! Very important!
end;
begin
Assert(Assigned(traceStrs));
traceStrs.Clear;
if not FBinaryFileOpen then
begin
OpenFile;
end;
ReadAtIndex := FLastIndex;
NextReadIndex;
Safety := 0; // prevents endless looping.
while True do
begin
if (ReadAtIndex = FLastIndex) or (Safety > FStorageSize) then
break;
Seek(FBinaryFile, ReadAtIndex);
Read(FBinaryFIle, FFileRec);
if FFileRec.msg[0] <> chr(0) then
begin
traceStrs.Add(FFileRec.msg);
end;
Inc(Safety);
NextReadIndex;
end;
end;
end.