1

すぐにログ ファイルを実装するサービス アプリケーションがあります。ログ ファイルを保存する方法を書き始める前に、ログをリアルタイムで表示するための小さな単純なフォーム アプリケーションを使用できるようにする必要があるという別の要件があります。つまり、サービスがログに何かを書き込んだ場合、それをファイルに保存するだけでなく、他のアプリケーションがログに記録された内容をすぐに認識して表示する必要があります。

このアプリがこのファイルを常に開いて最近の変更をチェックし、新しいものをロードするという汚い解決策があります。しかし、これは非常にずさんで重いです。一方で、サーバー/クライアント ソケット ペアを作成して、そこから監視することもできますが、TCP/IP を使用して 1 つの文字列を送信するのは少しオーバーロードだと思います。ファイル方式を考えているのですが、どうすれば重くならないのでしょうか?つまり、ログ ファイルが 100 万行に増加したとします。ファイル全体をロードするのではなく、ファイルの最後で新しいデータを確認する必要があるだけです。5 秒までの遅延も問題ありませんが、それは「リアルタイム」に矛盾します。

私がよく知っているファイルを読み書きする唯一の方法は、ファイルを開いたまま/ロックしたままにして、ファイルのすべての内容を読み取ることであり、ファイルの末尾から部分的にのみ読み取り、それを保護する方法がわかりませんアクセスしようとしている両方のアプリケーションから。

4

6 に答える 6

4

あなたが求めているのは、まさに私の会社のプロジェクトの1つで私がしていることです。

アウトプロセスのCOMオブジェクトをホストするサービスがあるため、すべてのアプリが中央のログファイルにメッセージを書き込むことができます。次に、同じCOMオブジェクトを使用して、ログが記録されるたびにサービスから直接通知を受信する別のビューアアプリがあります。ファイルの変更。COMオブジェクトは、ログファイルが物理的にどこにあるかをビューアに通知するため、ビューアは必要なときにファイルを直接開くことができます。

受信した通知ごとに、ビューアは新しいファイルサイズを確認し、最後の通知以降に書き込まれた新しいバイトのみを読み取ります(ビューアは以前のファイルサイズを追跡します)。以前のバージョンでは、サービスが実際に個々のログエントリをビューアに直接プッシュしていましたが、負荷が高く、大量のトラフィックをふるいにかけるため、その機能を削除して、ビューアにデータの読み取りを処理させることにしました。代わりに、そうすることで、一度に複数のログエントリをより効率的に読み取ることができます。

サービスとビューアの両方で、ログファイルが同時に開かれます。サービスがログファイルを作成/開くと、読み取り専用共有を使用した読み取り/書き込みアクセスにファイルが設定されます。ビューアーがファイルを開くと、ファイルを読み取り/書き込み共有を使用した読み取り専用アクセスに設定します(これにより、サービスは引き続きファイルに書き込むことができます)。

言うまでもなく、サービスとビューアは同じマシンで実行されるため、同じローカルファイルにアクセスできます(リモートファイルは使用されません)。このサービスには、TCP / IPを介してログエントリを別のマシンで実行されているサービスのリモートインスタンスに転送する機能があります(そのマシンで実行されているビューアはそれらを見ることができます)。

于 2012-08-29T23:49:02.827 に答える
3

当社のオープン ソースTSynLogクラスは、お客様のほとんどのニーズに対応します。すでに安定しており、証明されています (サービスを含む実際のアプリケーションで使用されています)。

主に高速ロギング (レベルの階層ではなくレベルのセット)、スタック トレースによる例外インターセプト、およびカスタム ロギング (ログ内の JSON としてのオブジェクトのシリアル化を含む) を備えています。

顧客側のメソッド プロファイラーログ ビューアーなどの追加機能もあります。

ログ ファイルは生成中にロックされます。それらを読み取ることはできますが、変更することはできません。

Delphi 5 から XE2 まで、完全にオープン ソースで、毎日更新されます。

于 2012-08-30T05:40:20.450 に答える
2

この記事を見てください。

TraceTool 12.4: Trace のスイスアーミー ナイフ

于 2012-08-30T23:37:18.880 に答える
2

これは完全にくだらない答えのように聞こえるかもしれませんが..

私は Gurock Softwares Smart Inspect を使用 ています。リモートマシンでもリアルタイム..ローカルファイルに送信できます..

それはおそらくあなたの問題に対する有用な答え、または赤いニシンです-少し型にはまらないですが、後で組み込む価値があると感じる追加機能があります(何かがひどくうまくいかない場合に情報をキャプチャするのに最適など)

于 2012-08-29T22:06:49.813 に答える
2

何年も前に、循環バッファー バイナリ ファイル トレース ロギング システムを作成しました。このシステムは、ファイルが無限に大きくなるという問題を回避し、必要に応じて問題を確認できるなど、必要な機能を提供してくれました。トレースバッファを無視できます。

ただし、継続的なオンライン システムが必要な場合は、ファイルをまったく使用しません。

私がファイルを使用したのは、ファイルのような永続性が本当に必要であり、リスナー アプリを実行する必要がなかったからです。私は単純にファイル ソリューションが欲しかったので、誰かが今すぐ「聞いている」かどうかに関係なくログを記録したかったのですが、ログ ファイルで数百メガバイトを使い果たすことを心配していたので、際限なく成長するテキスト ログを使用しませんでした。 、そして 250 メガバイトのハード ドライブがいっぱいになります。1 TB のハードディスクの時代には、そのような懸念はほとんどありません。

David が言うように、クライアント サーバー ソリューションが最適であり、実際には複雑ではありません。

しかし、以前の私の場合と同様に、ファイルを好むかもしれません。ビューアー アプリは、クラッシュ後に実行した事後分析ツールとしてのみ起動しました。これは MadExcept やそれに類するものが登場する前のことだったので、死んだばかりのアプリがいくつかあり、何が起こったのか知りたいと思っていました。

循環バッファーを使用する前は、sys-internals DebugView や OutputDebugString などのデバッグ ビュー ツールを使用していましたが、DebugView を起動する前にクラッシュが発生したときは役に立ちませんでした。

ファイルベースのログ (バイナリ) は、バイナリ ファイルの作成を許可した数少ない例の 1 つです。私は通常、バイナリファイルが嫌いです。しかし、固定長のバイナリ レコードを使用せずに、循環バッファーを作成しようとするだけです。

サンプルユニットはこちら。これを 1997 年ではなく今書いていたら、「File of record」を使用しなかったでしょう。

このユニットを拡張してリアルタイム ビューアーとして使用できるようにするには、バイナリ ファイルの日時スタンプを確認し、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.
于 2012-08-30T02:02:56.737 に答える
1

私の提案は、ログ ファイルが毎日「ロールオーバー」するような方法でログを実装することです。たとえば、午前 0 時に、ログ コードによってログ ファイル (MyLogFile.log など) の名前が日付付き/アーカイブ バージョン (MyLogFile-30082012.log など) に変更され、新しい空の「ライブ」ログ (再び MyLogFile.log など) が開始されます。

次に、BareTailのようなものを使用して「ライブ」/毎日のログ ファイルを監視するだけです。

これが最もネットワーク効率の高いアプローチではないことは承知していますが、かなりシンプルで、「ライブ」の要件を満たしています。

于 2012-08-29T22:41:25.867 に答える