寫一個日誌類用於跟蹤調試

根據本身的工做須要,借鑑了網上一些分析和嘗試,本身寫了一個日誌的單元用於服務器的跟蹤調試。api

unit LogUnit;

interface

uses
  System.Classes,System.SysUtils,System.Generics.Collections,Windows,Forms,IOUtils,
  Vcl.StdCtrls,Winapi.Messages;

const FLF = #13#10; // 換行符

type
  /// <summary>負責寫日誌的線程類</summary>
  TWriteLogToFileThread = class(TThread)
  private
    FCSLock: TRTLCriticalSection; //臨界區
    FLogFileSteam : TFileStream;
    FLogType : string;
    FUIControl : TComponent;
    FLogBuff,FBuffA,FBuffB:TMemoryStream;
    FBegCount:DWord; // 開始寫文件的時間
    function getLogFileName: string;
  protected
    procedure WriteToFile();
    procedure Execute();override;
    procedure WriteLog(const InBuff:Pointer;InSize:Integer);overload;
  public
    constructor Create(ALogType : string; AUIControl : TComponent=nil);
    destructor  Destroy();override;

    procedure WriteLog(const Msg:string);overload;

    property FileName:string read getLogFileName;
  end;

  /// <summary>日誌管理基類</summary>
  TLogClass = class
  private
    FWriteLogThreadList : TWriteLogToFileThread;
  protected
    /// <summary>日誌類型</summary>
    /// <remarks>純虛函數,子類覆寫該函數</remarks>
    function GetLogType : string; virtual; abstract;
  public
    /// <summary>構造函數</summary>
    /// <param name="ALogType">日誌類型,用於肯定日誌文件的文件名。</param>
    /// <param name="AUIControl">顯示日誌的可視化控件。</param>
    constructor Create(AUIControl : TComponent=nil); virtual;
    destructor Destroy; override;

    /// <summary>記錄日誌</summary>
    procedure WriteLog(const Msg:string);
    function LogFileName : string;
  end;

  /// <summary>服務器端通用日誌類</summary>
  TServerLog = class(TLogClass)
  protected
    /// <summary>覆蓋父類的虛函數,輸出日誌類型描述。</summary>
    function GetLogType : string; override;
  end;

  /// <summary>服務器端內核工做日誌類</summary>
  TServerCoreLog = class(TLogClass)
  protected
    /// <summary>覆蓋父類的虛函數,輸出日誌類型描述。</summary>
    function GetLogType : string; override;
  end;

  /// <summary>日誌類管理類,包裝了一組日誌類。</summary>
  TLogManager = class
  public
    class function ServerLog(AUIControl : TComponent=nil) : TServerLog;
    class function ServerCoreLog(AUIControl : TComponent=nil) : TServerCoreLog;
  end;

implementation

type
  /// <summary>負責建立日誌路徑的管理類</summary>
  /// <remarks>這個類是全局惟一的</remarks>
  TLogFilePathClass = class
  private
    FCSLock: TRTLCriticalSection; //臨界區
  public
    constructor Create;
    destructor Destroy; override;

    class function LogFilePathObject: TLogFilePathClass;
    function LogFilePath() : string;
  end;

var
  gInnerLogFilePathThread : TLogFilePathClass;
  gInnerServerLog : TServerLog;
  gInnerServerCoreLog : TServerCoreLog;

{ TLogClass }

constructor TLogClass.Create(AUIControl: TComponent);
begin
  FWriteLogThreadList := TWriteLogToFileThread.Create(GetLogType, AUIControl);
end;

destructor TLogClass.Destroy;
begin
  FWriteLogThreadList.Terminate;
  inherited;
end;

function TLogClass.LogFileName: string;
begin
  Result := FWriteLogThreadList.FileName;
end;

procedure TLogClass.WriteLog(const Msg: string);
begin
  FWriteLogThreadList.WriteLog(Msg);
end;

{ TLogFilePathClass }

constructor TLogFilePathClass.Create;
begin
  InitializeCriticalSection(FCSLock);
end;

destructor TLogFilePathClass.Destroy;
begin
  DeleteCriticalSection(FCSLock);
  inherited;
end;

function TLogFilePathClass.LogFilePath(): string;
var
  szPath : string;
begin
  EnterCriticalSection(FCSLock);
  try
//    szPath := Application.ExeName
    szPath := TDirectory.GetCurrentDirectory;
    szPath := szPath+'\log\'+FormatDateTime('yyyy-MM-dd', Now)+'\';
    if not TDirectory.Exists(szPath) then
      TDirectory.CreateDirectory(szPath);
    Exit(szPath);
  finally
    LeaveCriticalSection(FCSLock);
  end;
end;

class function TLogFilePathClass.LogFilePathObject: TLogFilePathClass;
begin
  if gInnerLogFilePathThread = nil then
    gInnerLogFilePathThread := TLogFilePathClass.Create;
  Exit(gInnerLogFilePathThread);
end;

{ TWriteLogToFileThread }

constructor TWriteLogToFileThread.Create(ALogType: string;
  AUIControl: TComponent);
var
  LogFileName : string;
begin
  if Trim(ALogType) = '' then
    raise exception.Create('ALogType not ""');

  inherited Create(TRUE);

  Self.FreeOnTerminate := True;

  InitializeCriticalSection(FCSLock);
  FLogType := ALogType;
  FUIControl := AUIControl;

   //隊列緩衝區A,B運行的時候,交替使用

  Self.FBuffA := TMemoryStream.Create();

  Self.FBuffA.Size := 1024 * 1024; //初始值能夠根據須要自行調整
  Self.FBuffB := TMemoryStream.Create();
  Self.FBuffB.Size := 1024 * 1024; //初始值能夠根據須要自行調整
  Self.FLogBuff := Self.FBuffA;

  LogFileName := getLogFileName;
  if FileExists(LogfileName) then
  begin
    FLogFileSteam := TFileStream.Create(LogFileName,fmOpenWrite or fmShareDenyWrite);
    FLogFileSteam.Position := FLogFileSteam.Size; //若是文件已經存在,數據進行追加
  end
  else
    FLogFileSteam := TFileStream.Create(LogFileName,fmCreate or fmShareDenyWrite);

   //啓動執行
   Self.Resume();
end;

destructor TWriteLogToFileThread.Destroy;
begin
  FBuffA.Free();
  FBuffB.Free();
  FLogFileSteam.Free();
  DeleteCriticalSection(FCSLock);
  inherited;
end;

procedure TWriteLogToFileThread.Execute;
begin
  inherited;
  FBegCount := GetTickCount();
  while(not Self.Terminated) do
  begin
    // 數據寫入磁盤的間隔,即每2秒寫一第二天志文件。
    if (GetTickCount() - FBegCount) >= 2000 then
    begin
      WriteToFile();
      FBegCount := GetTickCount();
    end
    else
      Sleep(200);
  end;
  WriteToFile();
end;

function TWriteLogToFileThread.getLogFileName: string;
begin
  Exit(TLogFilePathClass.LogFilePathObject.LogFilePath()+FLogType+'.LOG');
end;

procedure TWriteLogToFileThread.WriteLog(const InBuff: Pointer;
  InSize: Integer);
var
  TmpStr:string;
  Bytes : TBytes;
  lineCount: Integer;
begin
  TmpStr := FormatDateTime('YYYY-MM-DD hh:mm:ss zzz ',Now());
  EnterCriticalSection(FCSLock);
  try
    Bytes := TEnCoding.UTF8.GetBytes(TmpStr);
    FLogBuff.Write(Bytes, Length(Bytes));

    TmpStr := string(InBuff);
    Bytes := TEnCoding.UTF8.GetBytes(InBuff);
    FLogBuff.Write(Bytes, Length(Bytes));

    Bytes := TEnCoding.UTF8.GetBytes(FLF);
    FLogBuff.Write(Bytes, Length(Bytes));

    if FUIControl <> nil then
    begin
      if FUIControl is TMemo then
      begin
        lineCount := TMemo(FUIControl).Lines.Add(string(InBuff));
        //滾屏到最後一行
        SendMessage(TMemo(FUIControl).Handle,WM_VSCROLL,SB_LINEDOWN,0);
        // 設定一個最大顯示行數,若是超過這個行數,就清除。
        if lineCount >= 3 then
          TMemo(FUIControl).Clear;
      end;
    end;
  finally
    LeaveCriticalSection(FCSLock);
  end;
end;

procedure TWriteLogToFileThread.WriteLog(const Msg: string);
begin
  WriteLog(Pointer(Msg),Length(Msg));
end;

procedure TWriteLogToFileThread.WriteToFile;
var
  MS:TMemoryStream;
  LogFileName : string;
begin
  EnterCriticalSection(FCSLock);
  //交換緩衝區
  try
    MS := nil;
    if FLogBuff.Position > 0 then
    begin
      MS := FLogBuff;
      if FLogBuff = FBuffA then
        FLogBuff := FBuffB
      else
        FLogBuff := FBuffA;
     FLogBuff.Position := 0;
   end;
  finally
    LeaveCriticalSection(FCSLock);
  end;

  if MS = nil then
    Exit;

  //寫入文件
  try
    LogFileName := getLogFileName;
    // 若是日誌文件名(這裏實質是路徑)發生改變,則須要從新建立流。
    // 路徑的改變只有一個緣由:日期發生了改變。
    if FLogFileSteam.FileName <> LogFileName then
    begin
      if FileExists(LogfileName) then
      begin
        FLogFileSteam := TFileStream.Create(LogFileName,fmOpenWrite or fmShareDenyWrite);
        FLogFileSteam.Position := FLogFileSteam.Size; //若是文件已經存在,數據進行追加
      end
      else
        FLogFileSteam := TFileStream.Create(LogFileName,fmCreate or fmShareDenyWrite);
    end
    else
    // 不然的話直接寫流。
    begin
      FLogFileSteam.Write(MS.Memory^,MS.Position);
    end;
  finally
    MS.Position := 0;
  end;
end;

{ TServerLog }

function TServerLog.GetLogType: string;
begin
  Result := 'ServerLog';
end;

{ TLogManager }

class function TLogManager.ServerCoreLog(
  AUIControl: TComponent): TServerCoreLog;
begin
  if gInnerServerCoreLog = nil then
    gInnerServerCoreLog := TServerCoreLog.Create(AUIControl);
  Exit(gInnerServerCoreLog);
end;

class function TLogManager.ServerLog(AUIControl: TComponent): TServerLog;
begin
  if gInnerServerLog = nil then
    gInnerServerLog := TServerLog.Create(AUIControl);
  Exit(gInnerServerLog);
end;

{ TServerCoreLog }

function TServerCoreLog.GetLogType: string;
begin
  Result := 'ServerCoreLog';
end;

initialization
finalization
  if gInnerServerLog <> nil then
    gInnerServerLog.Free;
  if gInnerServerCoreLog <> nil then
    gInnerServerCoreLog.Free;
  if gInnerLogFilePathThread <> nil then
    gInnerLogFilePathThread.Free;

end.

寫這個單元的時候,主要考慮了幾個方面:服務器

1.性能,不能卡程序;多線程

此點經過多線程、流緩衝來解決。ide

2.接口調用方便;函數

此點經過TLogManager類封裝日誌對象輸出調用來解決。性能

3.易於擴展出不一樣的日誌類;spa

此點經過剝離TLogClass和TWriteLogToFileThread來實現,TWriteLogToFileThread負責流和文件的讀寫。TLogClass負責服務接口的提供。須要不一樣的日誌類時,只須要繼承TLogClass類,而不須要重複去處理流和多線程。線程

4.日誌文件組織結構清晰調試

經過日誌的類型和日期來組織日誌文件的結構。日誌

 

這裏遇到過一個麻煩,就是XE6寫流的時候,若是採起之前的老方式來寫的話,字符串只會截取到1/2的內容。

老的方式以下:

procedure TWriteLogToFileThread.WriteLog(const InBuff: Pointer;
  InSize: Integer);
var

   TmpStr:string;

begin
// ...
TmpStr := FormatDateTime('YYYY-MM-DD hh:mm:ss zzz ',Now()); FLogBuff.Write(TmpStr[
1],Length(TmpStr)); FLogBuff.Write(InBuff^,InSize); FLogBuff.Write(FLF[1],2); // ... end;

緣由多是Delphi的字符串流的格式仍是ansi的?

經過TEnCoding類把string轉成TBytes之後,再寫入流就OK了。

代碼以下:

var
  TmpStr:string;
  Bytes : TBytes;
begin
    // ...
    Bytes := TEnCoding.UTF8.GetBytes(TmpStr);
    FLogBuff.Write(Bytes, Length(Bytes));
    // ...
end;

 

單元文件

相關文章
相關標籤/搜索