Quick.Log.pas 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. { ***************************************************************************
  2. Copyright (c) 2016-2022 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 : 10/02/2022
  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. Posix.Unistd,
  41. {$ENDIF}
  42. SysUtils;
  43. type
  44. TMemoryLog = class
  45. private
  46. fLines : TStrings;
  47. fEnabled : Boolean;
  48. function GetLogText : string;
  49. public
  50. constructor Create;
  51. destructor Destroy; override;
  52. property Lines : TStrings read fLines write fLines;
  53. property Text : string read GetLogText;
  54. property Enabled : Boolean read fEnabled write fEnabled;
  55. end;
  56. TQuickLog = class
  57. private
  58. fLogFileName : string;
  59. fCurrentDateToFileName : Boolean;
  60. fHideHour : Boolean;
  61. fFMTName : string;
  62. fVerbose : TLogVerbose;
  63. fLimitLogSize : Int64;
  64. fCurrentLogSize : Int64;
  65. fShowEventTypes : Boolean;
  66. fShowHeaderInfo : Boolean;
  67. fCheckFileInUse : Boolean;
  68. fMemoryLog : TMemoryLog;
  69. function GetLogFileName : string;
  70. procedure WriteLog(cMsg : string);
  71. public
  72. property Verbose : TLogVerbose read fVerbose write fVerbose;
  73. property LogFileName : string read GetLogFileName;
  74. property HideHour : Boolean read fHideHour write fHideHour;
  75. property ShowEventType : Boolean read fShowEventTypes write fShowEventTypes;
  76. property ShowHeaderInfo : Boolean read fShowHeaderInfo write fShowHeaderInfo;
  77. property CheckFileInUse : Boolean read fCheckFileInUse write fCheckFileInUse;
  78. property MemoryLog : TMemoryLog read fMemoryLog write fMemoryLog;
  79. constructor Create;
  80. destructor Destroy; override;
  81. function SetLog(logname : string; AddCurrentDateToFileName : Boolean; LimitSizeInMB : Integer = 0) : Boolean;
  82. procedure Add(const cMsg : string; cEventType : TLogEventType = TLogEventType.etInfo); overload;
  83. procedure Add(const cFormat : string; cParams : array of TVarRec ; cEventType : TLogEventType = TLogEventType.etInfo); overload;
  84. end;
  85. var
  86. {$IF Defined(MACOS) OR Defined(ANDROID)}
  87. CS : TCriticalSection;
  88. {$ELSE}
  89. CS : TRTLCriticalSection;
  90. {$ENDIF}
  91. Log : TQuickLog;
  92. implementation
  93. constructor TMemoryLog.Create;
  94. begin
  95. inherited;
  96. fLines := TStringList.Create;
  97. fEnabled := False;
  98. end;
  99. destructor TMemoryLog.Destroy;
  100. begin
  101. if Assigned(fLines) then fLines.Free;
  102. inherited;
  103. end;
  104. function TMemoryLog.GetLogText : string;
  105. begin
  106. Result := fLines.Text;
  107. end;
  108. constructor TQuickLog.Create;
  109. begin
  110. inherited;
  111. fVerbose := LOG_ALL;
  112. fLimitLogSize := 0;
  113. fShowEventTypes := True;
  114. fShowHeaderInfo := True;
  115. fCheckFileInUse := False;
  116. fMemoryLog := TMemoryLog.Create;
  117. end;
  118. destructor TQuickLog.Destroy;
  119. begin
  120. if Assigned(fMemoryLog) then fMemoryLog.Free;
  121. inherited;
  122. end;
  123. {Returns date + time in english format (to add to filename}
  124. function TQuickLog.GetLogFileName : string;
  125. begin
  126. Result := '';
  127. if fCurrentDateToFileName then
  128. begin
  129. try
  130. //Checks if needs to show time or not
  131. if HideHour then Result := FormatDateTime('yyyymmdd', Now())
  132. else Result := FormatDateTime('yyyymmdd_hhmm', Now());
  133. Result := Format(fFMTName,[Result]);
  134. except
  135. Result := 'Error';
  136. end;
  137. end
  138. else Result := fLogFileName;
  139. end;
  140. {$IFDEF FPC}
  141. function OSVersion: String;
  142. begin
  143. Result:={$I %FPCTARGETOS%}+'-'+{$I %FPCTARGETCPU%};
  144. end;
  145. {$ENDIF}
  146. function TQuickLog.SetLog(logname : string; AddCurrentDateToFileName : Boolean; LimitSizeInMB : Integer = 0) : Boolean;
  147. begin
  148. if logname = '' then logname := TPath.GetDirectoryName(ParamStr(0)) + PathDelim + TPath.GetFileNameWithoutExtension(ParamStr(0)) + '.log';
  149. fFMTName := ExtractFilePath(logname) + ExtractFileNameWithoutExt(logname) + '_%s' + ExtractFileExt(logname);
  150. fHideHour := True;
  151. fCurrentDateToFileName := AddCurrentDateToFileName;
  152. fLogFileName := logname;
  153. //Checks if logfile is too big and deletes
  154. fLimitLogSize := LimitSizeInMB * 1024 * 1024;
  155. if (fLimitLogSize > 0) and (TFile.Exists(logname)) then
  156. begin
  157. try
  158. fCurrentLogSize := TFile.GetSize(logname);
  159. if fCurrentLogSize > fLimitLogSize then if DeleteFile(logname) then fCurrentLogSize := 0;
  160. except
  161. raise Exception.Create('can''t access to log file!');
  162. end;
  163. end;
  164. //Checks if it's in use
  165. {$IF NOT Defined(MACOS) AND NOT Defined(ANDROID)}
  166. if (fCheckFileInUse) and (TFile.IsInUse(logname)) Then fLogFileName := Format('%s_(1).%s',[ExtractFileNameWithoutExt(logname),ExtractFileExt(logname)]);
  167. {$ENDIF}
  168. //writes header info
  169. if fShowHeaderInfo then
  170. begin
  171. try
  172. Self.WriteLog(FillStr('-',70));
  173. Self.WriteLog(Format('Application : %s %s',[ExtractFilenameWithoutExt(ParamStr(0)),GetAppVersionFullStr]));
  174. Self.WriteLog(Format('Path : %s',[ExtractFilePath(ParamStr(0))]));
  175. Self.WriteLog(Format('CPU cores : %d',[CPUCount]));
  176. Self.WriteLog(Format('OS version : %s',{$IFDEF FPC}[OSVersion]{$ELSE}[TOSVersion.ToString]{$ENDIF}));
  177. Self.WriteLog(Format('Host : %s',[GetComputerName]));
  178. Self.WriteLog(Format('Username : %s',[Trim(GetLoggedUserName)]));
  179. Self.WriteLog(Format('Started : %s',[NowStr]));
  180. {$IFDEF MSWINDOWS}
  181. if IsService then Self.WriteLog('AppType : Service')
  182. else if System.IsConsole then Self.WriteLog('AppType : Console');
  183. {$ENDIF}
  184. if IsDebug then Self.WriteLog('Debug mode : On');
  185. Self.WriteLog(FillStr('-',70));
  186. except
  187. on E : Exception do Self.WriteLog('Can''t get info: ' + e.message);
  188. end;
  189. end;
  190. Result := True;
  191. end;
  192. procedure TQuickLog.Add(const cMsg : string; cEventType : TLogEventType = TLogEventType.etInfo);
  193. begin
  194. //Check Log Verbose level
  195. if cEventType in fVerbose then
  196. begin
  197. if fShowEventTypes then Self.WriteLog(Format('%s [%s] %s',[DateTimeToStr(Now()),EventStr[Integer(cEventType)],cMsg]))
  198. else Self.WriteLog(Format('%s %s',[DateTimeToStr(Now()),cMsg]));
  199. end;
  200. end;
  201. procedure TQuickLog.WriteLog(cMsg : string);
  202. var
  203. stream: TFileStream;
  204. logname : string;
  205. LBuffer : TBytes;
  206. LByteOrderMark: TBytes;
  207. LOffset : Integer;
  208. LEncoding, DestEncoding: TEncoding;
  209. begin
  210. //Checks if need to add date to filename
  211. logname := GetLogFileName;
  212. {$IF Defined(MACOS) OR Defined(ANDROID)}
  213. CS.Enter;
  214. {$ELSE}
  215. EnterCriticalSection(CS);
  216. {$ENDIF}
  217. try
  218. cMsg := cMsg + #13#10;
  219. LEncoding:= TEncoding.Unicode;
  220. DestEncoding := TEncoding.Unicode;
  221. SetLength(LBuffer, length(cMsg) * sizeof(char));
  222. if cMsg <> '' then Move(cMsg[1], lbuffer[0], Length(lbuffer));
  223. LOffset := TEncoding.GetBufferEncoding(LBuffer, LEncoding);
  224. LBuffer := LEncoding.Convert(LEncoding, DestEncoding, LBuffer,
  225. LOffset, Length(LBuffer) - LOffset);
  226. if TFile.Exists(logname) then
  227. begin
  228. stream := TFileStream.Create(logname, fmOpenReadWrite or fmShareDenyWrite);
  229. stream.Position := stream.Size;
  230. end
  231. else
  232. begin
  233. stream := TFileStream.Create(logname, fmCreate or fmShareDenyWrite);
  234. LByteOrderMark := DestEncoding.GetPreamble;
  235. stream.Write(LByteOrderMark[0], Length(LByteOrderMark));
  236. end;
  237. with stream do
  238. begin
  239. try
  240. Write(LBuffer[0], Length(LBuffer));
  241. fCurrentLogSize := fCurrentLogSize + Length(LBuffer);
  242. finally
  243. Free;
  244. end;
  245. end;
  246. //writes internal log
  247. if fMemoryLog.Enabled then
  248. begin
  249. fMemoryLog.Lines.Add(cMsg);
  250. end;
  251. //check if limits max size
  252. try
  253. if (fLimitLogSize > 0) and (fCurrentLogSize > fLimitLogSize) then if DeleteFile(logname) then fCurrentLogSize := 0;
  254. except
  255. //
  256. end;
  257. finally
  258. {$IF Defined(MACOS) OR Defined(ANDROID)}
  259. CS.Leave;
  260. {$ELSE}
  261. LeaveCriticalSection(CS);
  262. {$ENDIF}
  263. end;
  264. end;
  265. procedure TQuickLog.Add(const cFormat : string; cParams : array of TVarRec ; cEventType : TLogEventType = TLogEventType.etInfo);
  266. begin
  267. Self.Add(Format(cFormat,cParams),cEventType);
  268. end;
  269. initialization
  270. {$IF DEFINED(FPC) AND DEFINED(LINUX)}
  271. InitCriticalSection(CS);
  272. {$ELSE}
  273. {$IF Defined(MACOS) OR Defined(ANDROID)}
  274. CS := TCriticalSection.Create;
  275. {$ELSE}
  276. {$IFDEF DELPHILINUX}
  277. CS := TRTLCriticalSection.Create;
  278. {$ELSE}
  279. InitializeCriticalSection(CS);
  280. {$ENDIF}
  281. {$ENDIF}
  282. {$ENDIF}
  283. finalization
  284. {$IF DEFINED(FPC) AND DEFINED(LINUX)}
  285. DoneCriticalsection(CS);
  286. {$ELSE}
  287. {$IF Defined(MACOS) OR Defined(ANDROID) OR Defined(DELPHILINUX)}
  288. CS.Free;
  289. {$ELSE}
  290. DeleteCriticalSection(CS);
  291. {$ENDIF}
  292. {$ENDIF}
  293. end.