根據本身的工做須要,借鑑了網上一些分析和嘗試,本身寫了一個日誌的單元用於服務器的跟蹤調試。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;