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


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

    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;

    单元文件

  • 相关阅读:
    2019 学霸君java面试笔试题 (含面试题解析)
    2019 大众书网Java面试笔试题 (含面试题解析)
    2019 中细软java面试笔试题 (含面试题解析)
    2019 企叮咚java面试笔试题 (含面试题解析)
    js 去掉数组对象中的重复对象
    canvas霓虹雨
    nvm的安装
    socket.io 中文文档
    Nginx(三)------nginx 反向代理
    github入门到上传本地项目
  • 原文地址:https://www.cnblogs.com/codingnote/p/3844354.html
Copyright © 2020-2023  润新知