Quick.Log.pas 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 Kike Pérez
  3. Unit : Quick.Log
  4. Description : Threadsafe Log
  5. Author : Kike Pérez
  6. Version : 1.19
  7. Created : 10/04/2016
  8. Modified : 21/01/2019
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Log;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. {$IFDEF MSWINDOWS}
  26. Windows,
  27. {$ENDIF}
  28. Classes,
  29. Quick.Commons,
  30. {$IFNDEF FPC}
  31. System.IOUtils,
  32. {$IFDEF DELPHILINUX}
  33. Quick.SyncObjs.Linux.Compatibility,
  34. {$ENDIF}
  35. {$ELSE}
  36. Quick.Files,
  37. {$ENDIF}
  38. {$IF Defined(NEXTGEN) OR Defined(LINUX) or Defined(MACOS)}
  39. syncObjs,
  40. {$ENDIF}
  41. SysUtils;
  42. type
  43. TMemoryLog = class
  44. private
  45. fLines : TStrings;
  46. fEnabled : Boolean;
  47. function GetLogText : string;
  48. public
  49. constructor Create;
  50. destructor Destroy; override;
  51. property Lines : TStrings read fLines write fLines;
  52. property Text : string read GetLogText;
  53. property Enabled : Boolean read fEnabled write fEnabled;
  54. end;
  55. TQuickLog = class
  56. private
  57. fLogFileName : string;
  58. fCurrentDateToFileName : Boolean;
  59. fHideHour : Boolean;
  60. fFMTName : string;
  61. fVerbose : TLogVerbose;
  62. fLimitLogSize : Int64;
  63. fCurrentLogSize : Int64;
  64. fShowEventTypes : Boolean;
  65. fShowHeaderInfo : Boolean;
  66. fCheckFileInUse : Boolean;
  67. fMemoryLog : TMemoryLog;
  68. function GetLogFileName : string;
  69. procedure WriteLog(cMsg : string);
  70. public
  71. property Verbose : TLogVerbose read fVerbose write fVerbose;
  72. property LogFileName : string read GetLogFileName;
  73. property HideHour : Boolean read fHideHour write fHideHour;
  74. property ShowEventType : Boolean read fShowEventTypes write fShowEventTypes;
  75. property ShowHeaderInfo : Boolean read fShowHeaderInfo write fShowHeaderInfo;
  76. property CheckFileInUse : Boolean read fCheckFileInUse write fCheckFileInUse;
  77. property MemoryLog : TMemoryLog read fMemoryLog write fMemoryLog;
  78. constructor Create;
  79. destructor Destroy; override;
  80. function SetLog(logname : string; AddCurrentDateToFileName : Boolean; LimitSizeInMB : Integer = 0) : Boolean;
  81. procedure Add(const cMsg : string; cEventType : TLogEventType = TLogEventType.etInfo); overload;
  82. procedure Add(const cFormat : string; cParams : array of TVarRec ; cEventType : TLogEventType = TLogEventType.etInfo); overload;
  83. end;
  84. var
  85. {$IF Defined(MACOS) OR Defined(ANDROID)}
  86. CS : TCriticalSection;
  87. {$ELSE}
  88. CS : TRTLCriticalSection;
  89. {$ENDIF}
  90. Log : TQuickLog;
  91. implementation
  92. constructor TMemoryLog.Create;
  93. begin
  94. inherited;
  95. fLines := TStringList.Create;
  96. fEnabled := False;
  97. end;
  98. destructor TMemoryLog.Destroy;
  99. begin
  100. if Assigned(fLines) then fLines.Free;
  101. inherited;
  102. end;
  103. function TMemoryLog.GetLogText : string;
  104. begin
  105. Result := fLines.Text;
  106. end;
  107. constructor TQuickLog.Create;
  108. begin
  109. inherited;
  110. fVerbose := LOG_ALL;
  111. fLimitLogSize := 0;
  112. fShowEventTypes := True;
  113. fShowHeaderInfo := True;
  114. fCheckFileInUse := False;
  115. fMemoryLog := TMemoryLog.Create;
  116. end;
  117. destructor TQuickLog.Destroy;
  118. begin
  119. if Assigned(fMemoryLog) then fMemoryLog.Free;
  120. inherited;
  121. end;
  122. {Returns date + time in english format (to add to filename}
  123. function TQuickLog.GetLogFileName : string;
  124. begin
  125. Result := '';
  126. if fCurrentDateToFileName then
  127. begin
  128. try
  129. //Checks if needs to show time or not
  130. if HideHour then Result := FormatDateTime('yyyymmdd', Now())
  131. else Result := FormatDateTime('yyyymmdd_hhmm', Now());
  132. Result := Format(fFMTName,[Result]);
  133. except
  134. Result := 'Error';
  135. end;
  136. end
  137. else Result := fLogFileName;
  138. end;
  139. {$IFDEF FPC}
  140. function OSVersion: String;
  141. begin
  142. Result:={$I %FPCTARGETOS%}+'-'+{$I %FPCTARGETCPU%};
  143. end;
  144. {$ENDIF}
  145. function TQuickLog.SetLog(logname : string; AddCurrentDateToFileName : Boolean; LimitSizeInMB : Integer = 0) : Boolean;
  146. begin
  147. if logname = '' then logname := TPath.GetDirectoryName(ParamStr(0)) + '\' + TPath.GetFileNameWithoutExtension(ParamStr(0)) + '.log';
  148. fFMTName := ExtractFilePath(logname) + ExtractFileNameWithoutExt(logname) + '_%s' + ExtractFileExt(logname);
  149. fHideHour := True;
  150. fCurrentDateToFileName := AddCurrentDateToFileName;
  151. fLogFileName := logname;
  152. //Checks if logfile is too big and deletes
  153. fLimitLogSize := LimitSizeInMB * 1024 * 1024;
  154. if (fLimitLogSize > 0) and (TFile.Exists(logname)) then
  155. begin
  156. try
  157. fCurrentLogSize := TFile.GetSize(logname);
  158. if fCurrentLogSize > fLimitLogSize then if DeleteFile(logname) then fCurrentLogSize := 0;
  159. except
  160. raise Exception.Create('can''t access to log file!');
  161. end;
  162. end;
  163. //Checks if it's in use
  164. {$IF NOT Defined(MACOS) AND NOT Defined(ANDROID)}
  165. if (fCheckFileInUse) and (TFile.IsInUse(logname)) Then fLogFileName := Format('%s_(1).%s',[ExtractFileNameWithoutExt(logname),ExtractFileExt(logname)]);
  166. {$ENDIF}
  167. //writes header info
  168. if fShowHeaderInfo then
  169. begin
  170. try
  171. Self.WriteLog(FillStr('-',70));
  172. Self.WriteLog(Format('Application : %s %s',[ExtractFilenameWithoutExt(ParamStr(0)),GetAppVersionFullStr]));
  173. Self.WriteLog(Format('Path : %s',[ExtractFilePath(ParamStr(0))]));
  174. Self.WriteLog(Format('CPU cores : %d',[CPUCount]));
  175. Self.WriteLog(Format('OS version : %s',{$IFDEF FPC}[OSVersion]{$ELSE}[TOSVersion.ToString]{$ENDIF}));
  176. Self.WriteLog(Format('Host : %s',[GetComputerName]));
  177. Self.WriteLog(Format('Username : %s',[Trim(GetLoggedUserName)]));
  178. Self.WriteLog(Format('Started : %s',[NowStr]));
  179. {$IFDEF MSWINDOWS}
  180. if IsService then Self.WriteLog('AppType : Service')
  181. else if System.IsConsole then Self.WriteLog('AppType : Console');
  182. {$ENDIF}
  183. if IsDebug then Self.WriteLog('Debug mode : On');
  184. Self.WriteLog(FillStr('-',70));
  185. except
  186. on E : Exception do Self.WriteLog('Can''t get info: ' + e.message);
  187. end;
  188. end;
  189. Result := True;
  190. end;
  191. procedure TQuickLog.Add(const cMsg : string; cEventType : TLogEventType = TLogEventType.etInfo);
  192. begin
  193. //Check Log Verbose level
  194. if cEventType in fVerbose then
  195. begin
  196. if fShowEventTypes then Self.WriteLog(Format('%s [%s] %s',[DateTimeToStr(Now()),EventStr[Integer(cEventType)],cMsg]))
  197. else Self.WriteLog(Format('%s %s',[DateTimeToStr(Now()),cMsg]));
  198. end;
  199. end;
  200. procedure TQuickLog.WriteLog(cMsg : string);
  201. var
  202. stream: TFileStream;
  203. logname : string;
  204. LBuffer : TBytes;
  205. LByteOrderMark: TBytes;
  206. LOffset : Integer;
  207. LEncoding, DestEncoding: TEncoding;
  208. begin
  209. //Checks if need to add date to filename
  210. logname := GetLogFileName;
  211. {$IF Defined(MACOS) OR Defined(ANDROID)}
  212. CS.Enter;
  213. {$ELSE}
  214. EnterCriticalSection(CS);
  215. {$ENDIF}
  216. try
  217. cMsg := cMsg + #13#10;
  218. LEncoding:= TEncoding.Unicode;
  219. DestEncoding := TEncoding.Unicode;
  220. SetLength(LBuffer, length(cMsg) * sizeof(char));
  221. if cMsg <> '' then Move(cMsg[1], lbuffer[0], Length(lbuffer));
  222. LOffset := TEncoding.GetBufferEncoding(LBuffer, LEncoding);
  223. LBuffer := LEncoding.Convert(LEncoding, DestEncoding, LBuffer,
  224. LOffset, Length(LBuffer) - LOffset);
  225. if TFile.Exists(logname) then
  226. begin
  227. stream := TFileStream.Create(logname, fmOpenReadWrite or fmShareDenyWrite);
  228. stream.Position := stream.Size;
  229. end
  230. else
  231. begin
  232. stream := TFileStream.Create(logname, fmCreate or fmShareDenyWrite);
  233. LByteOrderMark := DestEncoding.GetPreamble;
  234. stream.Write(LByteOrderMark[0], Length(LByteOrderMark));
  235. end;
  236. with stream do
  237. begin
  238. try
  239. Write(LBuffer[0], Length(LBuffer));
  240. fCurrentLogSize := fCurrentLogSize + Length(LBuffer);
  241. finally
  242. Free;
  243. end;
  244. end;
  245. //writes internal log
  246. if fMemoryLog.Enabled then
  247. begin
  248. fMemoryLog.Lines.Add(cMsg);
  249. end;
  250. //check if limits max size
  251. try
  252. if (fLimitLogSize > 0) and (fCurrentLogSize > fLimitLogSize) then if DeleteFile(logname) then fCurrentLogSize := 0;
  253. except
  254. //
  255. end;
  256. finally
  257. {$IF Defined(MACOS) OR Defined(ANDROID)}
  258. CS.Leave;
  259. {$ELSE}
  260. LeaveCriticalSection(CS);
  261. {$ENDIF}
  262. end;
  263. end;
  264. procedure TQuickLog.Add(const cFormat : string; cParams : array of TVarRec ; cEventType : TLogEventType = TLogEventType.etInfo);
  265. begin
  266. Self.Add(Format(cFormat,cParams),cEventType);
  267. end;
  268. initialization
  269. {$IF DEFINED(FPC) AND DEFINED(LINUX)}
  270. InitCriticalSection(CS);
  271. {$ELSE}
  272. {$IF Defined(MACOS) OR Defined(ANDROID)}
  273. CS := TCriticalSection.Create;
  274. {$ELSE}
  275. InitializeCriticalSection(CS);
  276. {$ENDIF}
  277. {$ENDIF}
  278. finalization
  279. {$IF DEFINED(FPC) AND DEFINED(LINUX)}
  280. DoneCriticalsection(CS);
  281. {$ELSE}
  282. {$IF Defined(MACOS) OR Defined(ANDROID)}
  283. CS.Free;
  284. {$ELSE}
  285. DeleteCriticalSection(CS);
  286. {$ENDIF}
  287. {$ENDIF}
  288. end.