实时记录服务应用程序

时间:2012-08-29 21:52:30

标签: delphi logging delphi-xe2

我有一个服务应用程序,我将很快实现一个日志文件。在我开始编写如何保存日志文件之前,我还有另一个要求,即应该有一个小的简单表单应用程序可以实时查看日志。换句话说,如果服务将某些内容写入日志,则不仅应将其保存到文件中,而且其他应用程序应立即知道并显示记录的内容。

这个应用程序的脏解决方案是不断打开此文件并检查最近的更改,并加载任何新内容。但这非常草率和沉重。另一方面,我可以写一个服务器/客户端套接字对并通过那里监视它,但我认为使用TCP / IP发送一个字符串有点过载。我正在考虑使用文件方法,但我怎么能以一种不会那么重的方式制作它?换句话说,假设日志文件增长到100万行。我不想加载整个文件,我只需要检查文件的末尾是否有新数据。我也可以延迟5秒,但这与“实时”相矛盾。

读取/写入我熟悉的文件的唯一方法包括保持文件打开/锁定并读取文件的所有内容,我不知道如何只读取文件末尾的部分,以及保护它免受试图访问它的两个应用程序的影响。

6 个答案:

答案 0 :(得分:4)

您所要求的正是我在公司的一个项目中所做的。

它有一个托管进程外COM对象的服务,因此我们所有的应用程序都可以将消息写入中央日志文件,然后是一个单独的查看器应用程序,它使用同一个COM对象直接从服务接收通知每当日志文件更改时。 COM对象允许查看者知道日志文件的物理位置,以便查看者可以在需要时直接打开文件。

对于收到的每个通知,查看器检查新文件大小,然后仅读取自上次通知以来已写入的新字节(查看器记录以前的文件大小)。在早期版本中,我让服务实际上直接将每个单独的日志条目推送到查看器,但是在负载很重的情况下需要筛选大量流量,所以我最终取出该功能并让查看器处理读取数据相反,它可以更有效地一次读取多个日志条目。

服务和查看器都同时打开日志文件。当服务创建/打开日志文件时,它将文件设置为具有只读共享的读/写访问权限。当查看器打开文件时,它会将文件设置为具有读/写共享的只读访问权限(因此服务仍然可以写入该文件)。

毋庸置疑,服务和查看器都在同一台机器上运行,因此它们可以访问相同的本地文件(不使用远程文件)。虽然该服务确实具有通过TCP / IP将日志条目转发到另一台机器上运行的服务的远程实例的功能(然后在该机器上运行的查看器可以看到它们)。

答案 1 :(得分:3)

我们Open Source TSynLog class matches most of your needs - 它已经稳定并经过验证(用于实际应用,包括服务)。

它主要包括快速日志记录(具有一组级别,而不是级别的层次结构),具有堆栈跟踪的异常拦截和自定义日志记录(包括将对象序列化为日志中的JSON)。

您甚至还有一些其他功能,例如客户端方法分析器a log viewer

日志文件在生成期间被锁定:您可以阅读它们,而不是修改它们。

从Delphi 5到XE2,完全开源以及每日更新。

答案 2 :(得分:2)

这听起来像是一个完全疯狂的答案,但是......

我使用Gurock Softwares Smart Inspect .. http://www.gurock.com/smartinspect/ 它很棒,因为你可以发送图片,变量,并将它们记录下来,所以当你想要文本atm时,它非常适合在远程机器上实时观看你的应用..它可以将它发送到本地文件..

它可能是一个有用的答案,你的问题,或一个红色的鲱鱼 - 它有点不同寻常,但它有你的额外功能可能值得合并以后(如它的伟大的捕获信息,如果出现可怕的错误)

答案 3 :(得分:2)

多年前我编写了一个循环缓冲二进制文件跟踪日志记录系统,它避免了文件不断增长的问题,同时为我提供了我想要的功能,例如能够在我想要的时候看到问题,但是否则,只能忽略跟踪缓冲区。

但是,如果你想要一个连续的在线系统,那么根本就不会使用文件。

我使用的是文件,因为我确实想要类似文件的持久性,并且不需要运行任何监听器应用程序。我只是想要文件解决方案,因为我希望日志记录发生,无论是否有人现在正在“收听”,或者没有,但是没有使用无休止增长的文本日志,因为我担心在日志文件中使用数百个megs ,并填补我的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.

答案 4 :(得分:2)

答案 5 :(得分:1)

我的建议是以日志文件“每日滚动”的方式实现日志记录。例如。在午夜,您的日志代码将您的日志文件(例如MyLogFile.log)重命名为日期/存档版本(例如MyLogFile-30082012.log),并启动一个新的空“实时”日志(例如,再次MyLogFile.log)。

然后,只需使用BareTail之类的内容来监控“实时”/每日日志文件。

我接受这可能不是最具网络效率的方法,但它相当简单并且符合您的“实时”要求。