写一个日志类用于跟踪调试

根据自己的工作需要,借鉴了网上一些分析和尝试,自己写了一个日志的单元用于服务器的跟踪调试。

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.性能,不能卡程序;

此点通过多线程、流缓冲来解决。

2.接口调用方便;

此点通过TLogManager类封装日志对象输出调用来解决。

3.易于扩展出不同的日志类;

此点通过剥离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;

 

单元文件

你可能感兴趣的:(日志)