http://www.pfeng.org/archives/395

稳定轻量级的Delphi日志类

开发应用软件,有一个很重要的环节就是记录软件运行日志,delphi下有很多优秀的开源项目,如Log4D(http://sourceforge.net/projects/log4d/)等。考虑到日常应用的便捷性,自己写了一个简单的日志类,我把它命名为:Logger,支持同时显示到容器(TMemo、TListBox、TListView)和按日志级别决定是否写入文本,内部通过临界区的方法来保证线程安全,在子线程里可以安全使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
unit Logger;
//=======================================================================
//    日志类(TLoger) ver.1.0
//    PFeng  (http://www.pfeng.org / xxmc01#gmail.com)
//    2012/11/08
//    日志级别约定:
//          0 - Information
//          1 - Notice
//          2 - Warning
//          3 - Error
//=======================================================================
 
interface
 
uses Windows,Classes, SysUtils,StdCtrls,ComCtrls,ComObj,Messages;
 
const
  WRITE_LOG_DIR = 'log\'; //记录日志默认目录
  WRITE_LOG_MIN_LEVEL = 2; //记录日志的最低级别,小于此级别只显示不记录
  WRITE_LOG_ADD_TIME = True; //记录日志是否添加时间
  WRITE_LOG_TIME_FORMAT = 'hh:nn:ss.zzz';//记录日志添加时间的格式
  SHOW_LOG_ADD_TIME = True; //日志显示容器是否添加时间
  SHOW_LOG_TIME_FORMAT = 'yyyy/mm/dd hh:nn:ss.zzz'; //日志显示添加时间的格式
  SHOW_LOG_CLEAR_COUNT = 1000; //日志显示容器最大显示条数
 
type TLogger = class
  private
    FCSLock: TRTLCriticalSection; //临界区
    FFileStream: TFileStream; //文件流
    FLogShower: TComponent; //日志显示容器
    FLogDir: AnsiString; //日志目录
    FLogName: AnsiString; //日志名称
  protected
    procedure ShowLog(Log:AnsiString; const LogLevel:Integer = 0);
  public
    procedure WriteLog(Log:AnsiString; const LogLevel:Integer = 0);
      overload;
    procedure WriteLog(Log:AnsiString; const Args: array of const;
      const LogLevel:Integer = 0);overload;
    constructor Create(LogShower: TComponent;LogDir: AnsiString);
    destructor Destroy; override;
end;
 
implementation
 
  constructor TLogger.Create(LogShower: TComponent;LogDir: AnsiString);
  begin
    InitializeCriticalSection(FCSLock);
    FLogShower := LogShower;
    if Trim(LogDir) = '' then
      FLogDir := ExtractFilePath(ParamStr(0)) + WRITE_LOG_DIR
    else
      FLogDir := LogDir;
    if not DirectoryExists(FLogDir) then
    if not ForceDirectories(FLogDir) then
    begin
      raise Exception.Create('日志路径错误,日志类对象不能被创建');
    end;
  end;
 
  procedure TLogger.WriteLog(Log:AnsiString; const Args: array of const;
      const LogLevel:Integer = 0);
  begin
    WriteLog(Format(Log, args),LogLevel);
  end;
 
  procedure TLogger.WriteLog(Log:AnsiString; const LogLevel:Integer = 0);
  var
    logName: AnsiString;
    fMode: Word;
  begin
    EnterCriticalSection(FCSLock);
    try
      ShowLog(Log,LogLevel); //显示日志到容器
      if LogLevel >= WRITE_LOG_MIN_LEVEL  then
      begin
        logName := FormatDateTime('yyyymmdd',Now)+'.log';
        if FLogName <> logName then FLogName := logName;
        if FileExists(FLogDir+FLogName) then //如果当天的日志文件存在
          fMode := fmOpenWrite or fmShareDenyNone
        else
          fMode := fmCreate or fmShareDenyNone;
        if Assigned(FFileStream) then FreeAndNil(FFileStream);
        FFileStream := TFileStream.Create(FLogDir+FLogName,fmode);
        FFileStream.Position := FFileStream.Size; //追加到最后
        case LogLevel of
          0: Log := '[Information] ' + Log;
          1: Log := '[Notice] ' + Log;
          2: Log := '[Warning] ' + Log;
          3: Log := '[Error] ' + Log;
        end;
        if WRITE_LOG_ADD_TIME then
        Log := FormatDateTime(WRITE_LOG_TIME_FORMAT, Now) + ' '+ Log
               + #13#10;
        FFileStream.Write(PAnsiChar(Log)^, StrLen(PAnsiChar(Log)));
      end;
    finally
      LeaveCriticalSection(FCSLock);
    end;
  end;
 
  procedure TLogger.ShowLog(Log:AnsiString; const LogLevel:Integer = 0);
  var
    lineCount: Integer;
    listItem: TListItem;
  begin
    if FLogShower = nil then Exit;
    if (FLogShower is TMemo) then
    begin
      if SHOW_LOG_ADD_TIME then
      Log := FormatDateTime(SHOW_LOG_TIME_FORMAT, Now) + ' '+ Log;
      lineCount := TMemo(FLogShower).Lines.Add(Log);
      //滚屏到最后一行
      SendMessage(TMemo(FLogShower).Handle,WM_VSCROLL,SB_LINEDOWN,0);
      if lineCount >= SHOW_LOG_CLEAR_COUNT then
        TMemo(FLogShower).Clear;
    end
    else if (FLogShower is TListBox) then
    begin
      if SHOW_LOG_ADD_TIME then
      Log := FormatDateTime(SHOW_LOG_TIME_FORMAT, Now) + ' '+ Log;
      lineCount := TListBox(FLogShower).Items.Add(Log);
      SendMessage(TListBox(FLogShower).Handle,WM_VSCROLL,SB_LINEDOWN,0);
      if lineCount >= SHOW_LOG_CLEAR_COUNT then
        TListBox(FLogShower).Clear;
    end
    else if (FLogShower is TListView) then
    begin
      ListItem := TListView(FLogShower).Items.Add;
      if SHOW_LOG_ADD_TIME then
      ListItem.Caption := FormatDateTime(SHOW_LOG_TIME_FORMAT, Now);
      if Assigned(TListView(FLogShower).SmallImages) and
       (TListView(FLogShower).SmallImages.Count - 1 >= LogLevel) then
      ListItem.ImageIndex := LogLevel; //可以根据不同等级显示不同图片
      ListItem.SubItems.Add(Log);
      SendMessage(TListView(FLogShower).Handle,WM_VSCROLL,SB_LINEDOWN,0);
      if TListView(FLogShower).Items.Count >= SHOW_LOG_CLEAR_COUNT then
        TListView(FLogShower).Items.Clear;
    end
    else
      raise Exception.Create('日志容器类型不支持:' + FLogShower.ClassName);
  end;
 
  destructor TLogger.Destroy;
  begin
    DeleteCriticalSection(FCSLock);
  end;
 
end.

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uses Logger;
//...省略
var Logger:Tlogger;
//...省略
procedure TForm1.FormCreate(Sender: TObject);
begin
  Logger := TLogger.Create(Memo1,'');//使用默认路径
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  i:Integer;
begin
  for i := 0 to 199 do
  begin
    Logger.WriteLog('日志计数 %d',[i],2);
    //Logger.WriteLog('日志计数 '+inttostr(i),2);
  end;
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  Logger.Free;
end;

转载请注明:梧桐树下 » 稳定轻量级的Delphi日志类