Stage.Logger.pas 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit Stage.Logger;
  5. (*
  6. Activate USE_LOGGING in "GLS.inc" to turn on inner Scene logger.
  7. You may have only one instance of TGLSLogger
  8. To obtain it, call UserLog() function from any unit.
  9. *)
  10. interface
  11. {$I Stage.Defines.inc}
  12. uses
  13. Winapi.Windows,
  14. Winapi.Messages,
  15. Winapi.ShellApi,
  16. System.StrUtils,
  17. System.Classes,
  18. System.SysUtils,
  19. System.UITypes,
  20. System.SyncObjs;
  21. type
  22. // Levels of importance of log messages
  23. TLogLevel = (lkDebug, lkInfo, lkNotice, lkWarning, lkError, lkFatalError);
  24. // Log level setting type
  25. TLogLevels = set of TLogLevel;
  26. // What to do when number of messages exceeds message limit.
  27. TLogMessageLimitAction = (mlaContinue, mlaStopLogging, mlaHalt);
  28. var
  29. llMessageLimit: array [TLogLevel] of Integer = (
  30. MaxInt,
  31. MaxInt,
  32. MaxInt,
  33. 500,
  34. 100,
  35. 10
  36. );
  37. lkPrefix: array [TLogLevel] of string = (
  38. ' (D) ',
  39. ' (i) ',
  40. ' (M) ',
  41. ' (W) ',
  42. ' (Er) ',
  43. ' (!!) '
  44. );
  45. const
  46. llMax: TLogLevels = [lkDebug, lkInfo, lkNotice, lkWarning, lkError,
  47. lkFatalError];
  48. llMedium: TLogLevels = [lkNotice, lkWarning, lkError, lkFatalError];
  49. llMin: TLogLevels = [lkError, lkFatalError];
  50. type
  51. // Log date and time setting type
  52. TLogTimeFormat = (
  53. // doesn't output any time information
  54. lfNone,
  55. // include date in the log
  56. lfDate,
  57. // include time in the log
  58. lfTime,
  59. // include time in the log, including milliseconds
  60. lfTimeExact,
  61. // include date and time in the log
  62. lfDateTime,
  63. // include time elapsed since startup in the log
  64. lfElapsed);
  65. // How log is buffered.
  66. TLogBufferingMode = (lbmWriteEmidiatly, lbmWritePeriodically,
  67. lbmWriteInTheEnd);
  68. // Class reference to log session class
  69. CLogSession = class of TLogSession;
  70. TLogSession = class;
  71. // Thread that periodically flushes the buffer to disk.
  72. TLogBufferFlushThread = class(TThread)
  73. private
  74. FParent: TLogSession;
  75. protected
  76. procedure Execute; override;
  77. public
  78. constructor Create(const AParent: TLogSession);
  79. end;
  80. // Thread that checks file size and splits the file if nessesary.
  81. TLogCheckSizeThread = class(TThread)
  82. private
  83. FParent: TLogSession;
  84. protected
  85. procedure Execute; override;
  86. public
  87. constructor Create(const AParent: TLogSession);
  88. end;
  89. // Abstract Logger class
  90. TLogSession = class(TPersistent)
  91. private
  92. FBuffer: TStringList;
  93. FBuffered: Boolean;
  94. FBufferProcessingThread: TLogBufferFlushThread;
  95. FCheckLogSizeThread: TLogCheckSizeThread;
  96. FFlushBufferPeriod: Integer;
  97. FLogFile: Text; // TextFile.
  98. FDestroying: Boolean;
  99. FOriginalLogFileName: string; // Original name
  100. FCurrentLogFileName: string;
  101. // Current log file, if original exceeded certain size limit.
  102. FUsedLogFileNames: TStringList; // List of all created log files.
  103. FLogLevels: TLogLevels;
  104. FEnabled: Boolean;
  105. FBufferCriticalSection: TCriticalSection;
  106. FFileAccessCriticalSection: TCriticalSection;
  107. FModeTitles: array [TLogLevel] of string;
  108. FLogKindCount: array [TLogLevel] of Integer;
  109. FLogThreadId: Boolean;
  110. FMessageLimitAction: TLogMessageLimitAction;
  111. // Determines which date or time to include in the log
  112. FTimeFormat: TLogTimeFormat;
  113. // Startup timestamp in milliseconds
  114. FStartedMs: Cardinal;
  115. FLogFileMaxSize: Integer;
  116. FCheckFileSizePeriod: Integer;
  117. FDisplayLogOnExitIfItContains: TLogLevels;
  118. FWriteInternalMessages: Boolean;
  119. FDisplayErrorDialogs: Boolean;
  120. {$IFNDEF USE_LOGGING}
  121. constructor OnlyCreate;
  122. {$ENDIF}
  123. procedure SetBuffered(const Value: Boolean);
  124. procedure SetMode(const NewMode: TLogLevels);
  125. procedure ChangeBufferedState();
  126. procedure SetEnabled(const Value: Boolean);
  127. procedure SetLogFileMaxSize(const Value: Integer);
  128. protected
  129. procedure PrintLogLevels();
  130. procedure PrintLogStatistics();
  131. function AttachLogFile(const AFileName: string;
  132. const AResetFile: Boolean = True): Boolean;
  133. procedure ClearLogsInTheSameDir();
  134. procedure BackUpOldLogs(const ACurrentLogFileName: string);
  135. procedure CreateNewLogFileIfNeeded();
  136. // Appends a string to log. Thread-safe.
  137. procedure AppendLog(const AString: string; const ALevel: TLogLevel;
  138. const ALogTime: Boolean = True);
  139. // Writes string to log. Returns True if everything went ok.
  140. function DoWriteToLog(const AString: string): Boolean;
  141. // Writes FBuffer to log. Returns True if everything went ok.
  142. function DoWriteBufferToLog(): Boolean;
  143. // Resets log. Returns True if everything went ok.
  144. function DoResetLog: Boolean;
  145. public
  146. // Initializes a log session with the specified log file name, time and level settings
  147. constructor Init(const AFileName: string; const ATimeFormat: TLogTimeFormat;
  148. const ALevels: TLogLevels; const ALogThreadId: Boolean = True;
  149. const ABuffered: Boolean = False; const AMaxSize: Integer = 0;
  150. const ABackUpOldLogs: Boolean = False;
  151. const AClearOldLogs: Boolean = True;
  152. const AWriteInternalMessages: Boolean = True); virtual;
  153. destructor Destroy; override;
  154. // General Logging procedures
  155. procedure Log(const Desc: string; const Level: TLogLevel = lkInfo);
  156. procedure LogAdv(const args: array of const;
  157. const ALevel: TLogLevel = lkError);
  158. procedure LogException(const E: Exception; const aFunctionName: string;
  159. const args: array of const; const ALevel: TLogLevel = lkError);
  160. (* Logs a string Desc if Level
  161. matches current USE_LOGGING level (see @Link(LogLevels)) *)
  162. procedure LogDebug(const Desc: string);
  163. procedure LogInfo(const Desc: string);
  164. procedure LogNotice(const Desc: string);
  165. procedure LogWarning(const Desc: string);
  166. procedure LogError(const Desc: string);
  167. procedure LogFatalError(const Desc: string);
  168. procedure LogEmtryLine();
  169. // Logs a formatted string assembled from a format string and an array of arguments.
  170. procedure LogDebugFmt(const Desc: string; const args: array of const);
  171. procedure LogInfoFmt(const Desc: string; const args: array of const);
  172. procedure LogNoticeFmt(const Desc: string; const args: array of const);
  173. procedure LogWarningFmt(const Desc: string; const args: array of const);
  174. procedure LogErrorFmt(const Desc: string; const args: array of const);
  175. procedure LogFatalErrorFmt(const Desc: string; const args: array of const);
  176. // Mics procedures.
  177. procedure DisplayLog();
  178. procedure FlushBuffer();
  179. // If log is buffered, calling this will flush the buffer.
  180. // Set of levels which to include in the log
  181. property LogLevels: TLogLevels read FLogLevels write SetMode
  182. default [lkDebug, lkInfo, lkNotice, lkWarning, lkError, lkFatalError];
  183. property Enabled: Boolean read FEnabled write SetEnabled default True;
  184. property Buffered: Boolean read FBuffered write SetBuffered default False;
  185. property FlushBufferPeriod: Integer read FFlushBufferPeriod
  186. write FFlushBufferPeriod default 5000; // In ms.
  187. property LogThreadId: Boolean read FLogThreadId write FLogThreadId
  188. default True;
  189. property DisplayErrorDialogs: Boolean read FDisplayErrorDialogs
  190. write FDisplayErrorDialogs default True;
  191. property MessageLimitAction: TLogMessageLimitAction read FMessageLimitAction
  192. write FMessageLimitAction default mlaHalt;
  193. property WriteInternalMessages: Boolean read FWriteInternalMessages
  194. write FWriteInternalMessages default True;
  195. // To always display log, put all log types. To never display log, leave this empty.
  196. property DisplayLogOnExitIfItContains: TLogLevels
  197. read FDisplayLogOnExitIfItContains write FDisplayLogOnExitIfItContains
  198. default [lkDebug, lkInfo, lkNotice, lkWarning, lkError, lkFatalError];
  199. (* If LogFileMaxSize is not 0, then:
  200. 1) At start, all logs with the same extention will be deleted.
  201. 2) All logs wil be periodically cheked for FileSize.
  202. New log file will be created when this size exceeds limit. *)
  203. property LogFileMaxSize: Integer read FLogFileMaxSize
  204. write SetLogFileMaxSize default 0; // In bytes, limited to 2Gb.
  205. property CheckFileSizePeriod: Integer read FCheckFileSizePeriod
  206. write FCheckFileSizePeriod default 4000; // In ms.
  207. end;
  208. // Abstract class for control logging.
  209. TGLSLogger = class(TComponent)
  210. private
  211. FReplaceAssertion: Boolean;
  212. FTimeFormat: TLogTimeFormat;
  213. FLogLevels: TLogLevels;
  214. FLog: TLogSession;
  215. procedure SetReplaceAssertion(Value: Boolean);
  216. function GetLog: TLogSession;
  217. public
  218. constructor Create(AOwner: TComponent); override;
  219. destructor Destroy; override;
  220. // Set component primary and then UserLog return it's log
  221. procedure DoPrimary;
  222. property Log: TLogSession read GetLog;
  223. published
  224. property ReplaceAssertion: Boolean read FReplaceAssertion
  225. write SetReplaceAssertion default False;
  226. // Only design time sets. Define Log initial properties
  227. property TimeFormat: TLogTimeFormat read FTimeFormat write FTimeFormat
  228. default lfElapsed;
  229. property LogLevels: TLogLevels read FLogLevels write FLogLevels
  230. default [lkDebug, lkInfo, lkNotice, lkWarning, lkError, lkFatalError];
  231. end;
  232. TIDELogProc = procedure(const AMsg: string);
  233. // Return logger wich created by TGLSLogger component
  234. function UserLog: TLogSession;
  235. function ReadLine(var TextFile: Text): string;
  236. (* Inner logger.
  237. Converted to a function, because in case of a DLL and main app using this module,
  238. log is written to the same file on initialization and finalization,
  239. which is not what one might want. This also allows to create a GLSLogger with
  240. custom parameters for user's application, for example a different log path
  241. (Often the EXE application directory is read-only).
  242. *)
  243. function GLSLogger(): TLogSession;
  244. procedure UseCustomGLSLogger(const ALogger: TLogSession);
  245. function ConstArrayToString(const Elements: array of const): String;
  246. var
  247. vIDELogProc: TIDELogProc;
  248. // --------------------------------------------------------------------------
  249. implementation
  250. // --------------------------------------------------------------------------
  251. var
  252. v_GLSLogger: TLogSession;
  253. vAssertErrorHandler: TAssertErrorProc;
  254. vCurrentLogger: TGLSLogger;
  255. // Inner logger. Create on first use, not in unit initialization. }
  256. function GLSLogger(): TLogSession;
  257. begin
  258. if v_GLSLogger = nil then
  259. begin
  260. {$IFDEF USE_LOGGING}
  261. v_GLSLogger := TLogSession.Init(Copy(ExtractFileName(ParamStr(0)), 1,
  262. Length(ExtractFileName(ParamStr(0))) - Length(ExtractFileExt(ParamStr(0)))
  263. ) + '.log', lfElapsed, llMax);
  264. {$ELSE}
  265. v_GLSLogger := TLogSession.OnlyCreate;
  266. {$ENDIF}
  267. end;
  268. Result := v_GLSLogger;
  269. end;
  270. procedure UseCustomGLSLogger(const ALogger: TLogSession);
  271. begin
  272. if (v_GLSLogger <> nil) then
  273. v_GLSLogger.Destroy;
  274. v_GLSLogger := ALogger;
  275. end;
  276. const
  277. // VarRec -> String
  278. vTypeDesc: Array [0 .. 16] of String = ('vtInteger', 'vtBoolean', 'vtChar',
  279. 'vtExtended', 'vtString', 'vtPointer', 'vtPChar', 'vtObject', 'vtClass',
  280. 'vtWideChar', 'vtPWideChar', 'vtAnsiString', 'vtCurrency', 'vtVariant',
  281. 'vtInterface', 'vtWideString', 'vtInt64');
  282. vTypeAsSring: Array [0 .. 17] of String = ('Integer : ', 'Boolean : ',
  283. 'Char : ', 'Extended : ', 'String : ', 'Pointer : ',
  284. 'PChar : ', 'TObject : ', 'Class : ', 'WideChar : ',
  285. 'PWideChar : ', 'AnsiString : ', 'Currency : ', 'Variant : ',
  286. 'Interface : ', 'WideString : ', 'Int64 : ', '#HLType : ');
  287. // Function from HotLog by Olivier Touzot "QnnO".
  288. function GetOriginalValue(const s: String): String;
  289. // Called to remove the false 'AnsiString :' assertion, for pointers and objects
  290. begin
  291. Result := RightStr(s, Length(s) - 19);
  292. end;
  293. // Function from HotLog by Olivier Touzot "QnnO".
  294. function VarRecToStr(const vr: TVarRec): String;
  295. // See D6PE help topic "TVarRec"
  296. begin
  297. Result := vTypeAsSring[vr.VType] + ' ';
  298. try
  299. with vr do
  300. case VType of
  301. vtInteger: Result := Result + IntToStr(VInteger);
  302. vtBoolean: Result := Result + BoolToStr(VBoolean, True);
  303. vtChar: Result := Result + string(VChar);
  304. vtExtended: Result := Result + FloatToStr(VExtended^);
  305. vtString: Result := Result + string(VString^);
  306. // maintened in case of future need, but will actually not arrive.
  307. vtPointer: Result := Result + '^(' + Format('%P', [(addr(VPointer))]) + ')';
  308. vtPChar: Result := Result + string(VPChar);
  309. // ...
  310. vtObject:
  311. begin
  312. if VObject = Nil Then
  313. Result := Result + '^(NIL)'
  314. else
  315. Result := Result + VObject.classname;
  316. end;
  317. // ...
  318. vtClass: Result := Result + VClass.classname;
  319. vtWideChar: Result := Result + string(VWideChar);
  320. vtPWideChar: Result := Result + VPWideChar;
  321. vtAnsiString: Result := Result + string(VAnsiString);
  322. vtCurrency: Result := Result + CurrToStr(VCurrency^);
  323. vtVariant: Result := Result + string(VVariant^);
  324. vtInterface: Result := Result + '(Interfaced object)';
  325. vtWideString: Result := Result + string(VWideString^);
  326. vtInt64: Result := Result + IntToStr(VInt64^);
  327. else
  328. Result := Result + Format('[#HLvrType(%d)]', // "Else" not possible...
  329. [Integer(vr.VType)]); // ...with D6, but laters ?
  330. end; { case }
  331. EXCEPT
  332. Result := Result + Format('[#HLvrValue(%s)]', [vTypeDesc[vr.VType]]);
  333. end;
  334. end;
  335. // Function from HotLog by Olivier Touzot "QnnO".
  336. function GetBasicValue(const s: String; vKind: Byte): String;
  337. var
  338. iTmp: Integer;
  339. wasTObject: Boolean;
  340. begin
  341. Result := s;
  342. If s = '' then
  343. exit;
  344. try
  345. iTmp := Pos('$_H_', s);
  346. wasTObject := (Pos('$_H_TObject', s) > 0);
  347. if (iTmp > 0) then
  348. Result := GetOriginalValue(s); // converts fake strings back to original
  349. Result := RightStr(Result, Length(Result) - 15);
  350. // From now on, works on "result"
  351. if (vKind In [vtString, vtAnsiString, vtWideString, vtPChar, vtWideChar,
  352. vtPWideChar]) And Not(wasTObject) then
  353. exit
  354. else
  355. begin
  356. iTmp := Pos(' ', Result);
  357. If (iTmp > 0) and (iTmp < Length(Result)) then
  358. Result := LeftStr(Result, iTmp);
  359. end;
  360. EXCEPT
  361. ;
  362. end;
  363. end;
  364. // Function from HotLog by Olivier Touzot "QnnO".
  365. function ConstArrayToString(const Elements: array of const): String;
  366. // -2-> Returns à string, surrounded by parenthesis : '(elts[0]; ...; elts[n-1]);'
  367. // ("Basic infos" only.)
  368. var
  369. i: Integer;
  370. s, sep: String;
  371. Begin
  372. TRY
  373. if Length(Elements) = 0 then
  374. begin
  375. Result := '';
  376. exit;
  377. end;
  378. Result := '(';
  379. sep := '; ';
  380. for i := Low(Elements) to High(Elements) do
  381. begin
  382. s := VarRecToStr(Elements[i]);
  383. Result := Result + GetBasicValue(s, Elements[i].VType) + sep;
  384. end;
  385. Result := LeftStr(Result, Length(Result) - 2) + ');';
  386. // replaces last ", " by final ");".
  387. except
  388. Result := '[#HLvrConvert]';
  389. eND;
  390. end;
  391. function UserLog: TLogSession;
  392. begin
  393. if Assigned(vCurrentLogger) then
  394. Result := vCurrentLogger.Log
  395. else
  396. Result := nil;
  397. end;
  398. function RemovePathAndExt(const AFileName: string): string;
  399. var
  400. lExtIndex: Integer;
  401. begin
  402. Result := ExtractFileName(AFileName);
  403. lExtIndex := Pos(ExtractFileExt(Result), Result);
  404. Result := Copy(Result, 1, lExtIndex - 1);
  405. end;
  406. procedure LogedAssert(const Message, FileName: string; LineNumber: Integer;
  407. ErrorAddr: Pointer);
  408. begin
  409. UserLog.Log(Message + ': in ' + FileName + ' at line ' +
  410. IntToStr(LineNumber), lkError);
  411. Abort;
  412. end;
  413. function FileSize(const AFileName: String): Integer;
  414. var
  415. sr: TSearchRec;
  416. begin
  417. if FindFirst(AFileName, faAnyFile, sr) = 0 then
  418. begin
  419. Result := sr.Size;
  420. FindClose(sr);
  421. end
  422. else
  423. Result := -1;
  424. end;
  425. function ReadLine(var TextFile: Text): string;
  426. var
  427. i: Word;
  428. var
  429. s: string;
  430. begin
  431. if EOF(TextFile) then
  432. exit;
  433. i := 1;
  434. repeat
  435. readln(TextFile, s);
  436. until (s <> '') and (s[1] <> '#') or EOF(TextFile);
  437. if s <> '' then
  438. begin
  439. while s[i] = ' ' do
  440. inc(i);
  441. if i = Length(s) then
  442. s := ''
  443. else
  444. s := Copy(s, i, Length(s) - i + 1);
  445. end;
  446. Result := s;
  447. end;
  448. // ------------------
  449. // ------------------ TGLSLogger ------------------
  450. // ------------------
  451. constructor TGLSLogger.Create(AOwner: TComponent);
  452. begin
  453. inherited Create(AOwner);
  454. FTimeFormat := lfElapsed;
  455. FLogLevels := llMax;
  456. vAssertErrorHandler := AssertErrorProc;
  457. vCurrentLogger := Self;
  458. end;
  459. destructor TGLSLogger.Destroy;
  460. begin
  461. if vCurrentLogger = Self then
  462. vCurrentLogger := nil;
  463. if Assigned(FLog) then
  464. FLog.Destroy;
  465. inherited Destroy;
  466. end;
  467. function TGLSLogger.GetLog: TLogSession;
  468. begin
  469. if not Assigned(FLog) then
  470. FLog := TLogSession.Init(Name + '.log', FTimeFormat, FLogLevels);
  471. Result := FLog;
  472. end;
  473. procedure TGLSLogger.DoPrimary;
  474. begin
  475. vCurrentLogger := Self;
  476. end;
  477. procedure TGLSLogger.SetReplaceAssertion(Value: Boolean);
  478. begin
  479. if Value <> FReplaceAssertion then
  480. begin
  481. FReplaceAssertion := Value;
  482. case FReplaceAssertion of
  483. True:
  484. AssertErrorProc := @LogedAssert;
  485. False:
  486. AssertErrorProc := @vAssertErrorHandler;
  487. end;
  488. end;
  489. end;
  490. // ------------------
  491. // ------------------ TLogSession ------------------
  492. // ------------------
  493. procedure TLogSession.BackUpOldLogs(const ACurrentLogFileName: string);
  494. var
  495. sRec: TSearchRec;
  496. lLogFileName: string;
  497. lLogOriginalDir: string;
  498. lLogSaveDir: string;
  499. lLogExt: string;
  500. procedure SaveCurrentFile();
  501. var
  502. lErrorMessage: string;
  503. lFile: File;
  504. begin
  505. if not FDisplayErrorDialogs then
  506. RenameFile(lLogOriginalDir + sRec.Name, lLogSaveDir + sRec.Name)
  507. else
  508. begin
  509. lErrorMessage := 'Renaming of "%s" failed with error : %d. Try again?';
  510. while not RenameFile(lLogOriginalDir + sRec.Name,
  511. lLogSaveDir + sRec.Name) do
  512. begin
  513. Log(lErrorMessage + '(' + FModeTitles[lkError] + ' = ' + IntToStr(FLogKindCount[lkError]) + ')');
  514. SetBuffered(False);
  515. AssignFile(lFile, lLogOriginalDir + sRec.Name);
  516. CloseFile(lFile);
  517. Halt;
  518. end;
  519. end;
  520. end;
  521. begin
  522. lLogExt := ExtractFileExt(ACurrentLogFileName);
  523. lLogFileName := RemovePathAndExt(ACurrentLogFileName);
  524. lLogOriginalDir := ExtractFilePath(ACurrentLogFileName);
  525. lLogSaveDir := lLogOriginalDir + FormatDateTime('yyyy-mm-dd hh-nn-ss', Now);
  526. if not CreateDir(lLogSaveDir) then
  527. exit;
  528. lLogSaveDir := lLogSaveDir + PathDelim;
  529. If FindFirst(lLogOriginalDir + lLogFileName + '*' + lLogExt, faAnyFile,
  530. sRec) = 0 then
  531. begin
  532. try
  533. SaveCurrentFile();
  534. except
  535. end;
  536. while (FindNext(sRec) = 0) do
  537. try
  538. SaveCurrentFile();
  539. except
  540. end;
  541. FindClose(sRec);
  542. end;
  543. end;
  544. procedure TLogSession.SetBuffered(const Value: Boolean);
  545. begin
  546. if FBuffered = Value then
  547. exit;
  548. FBuffered := Value;
  549. ChangeBufferedState();
  550. end;
  551. procedure TLogSession.SetEnabled(const Value: Boolean);
  552. begin
  553. if (FEnabled = Value) then
  554. exit;
  555. FEnabled := Value;
  556. if (FEnabled) then
  557. Log('Logging session resumed')
  558. else
  559. Log('Logging session paused');
  560. end;
  561. procedure TLogSession.SetLogFileMaxSize(const Value: Integer);
  562. begin
  563. if FLogFileMaxSize = Value then
  564. exit;
  565. FLogFileMaxSize := Value;
  566. if FLogFileMaxSize > 0 then
  567. begin
  568. FCheckLogSizeThread := TLogCheckSizeThread.Create(Self);
  569. FCheckLogSizeThread.Start();
  570. end
  571. else
  572. begin
  573. FCheckLogSizeThread.Terminate();
  574. // Not really safe because we can wait forever.
  575. // But other methods known to me are platform-dependant.
  576. FCheckLogSizeThread.WaitFor();
  577. FCheckLogSizeThread.Free();
  578. end;
  579. end;
  580. procedure TLogSession.SetMode(const NewMode: TLogLevels);
  581. begin
  582. {$IFNDEF USE_LOGGING}
  583. if Self = v_GLSLogger then
  584. exit;
  585. {$ENDIF}
  586. FLogLevels := NewMode;
  587. PrintLogLevels();
  588. end;
  589. function TLogSession.DoResetLog: Boolean;
  590. begin
  591. try
  592. FFileAccessCriticalSection.Enter;
  593. Rewrite(FLogFile);
  594. CloseFile(FLogFile);
  595. FFileAccessCriticalSection.Leave;
  596. Result := True;
  597. except
  598. on E: Exception do
  599. begin
  600. // Ignore exceptions.
  601. Result := False;
  602. FFileAccessCriticalSection.Leave;
  603. end;
  604. end;
  605. end;
  606. function TLogSession.DoWriteBufferToLog: Boolean;
  607. var
  608. i: Integer;
  609. lLast: Integer;
  610. begin
  611. try
  612. // Open file.
  613. FFileAccessCriticalSection.Enter;
  614. Append(FLogFile);
  615. // Write buffer.
  616. lLast := FBuffer.Count - 1;
  617. for i := 0 to lLast do
  618. WriteLn(FLogFile, FBuffer[i]);
  619. // Clear buffer.
  620. FBufferCriticalSection.Enter;
  621. FBuffer.Clear();
  622. FBufferCriticalSection.Leave;
  623. // Close file.
  624. CloseFile(FLogFile);
  625. FFileAccessCriticalSection.Release();
  626. Result := True;
  627. except
  628. // Ignore exceptions.
  629. Result := False;
  630. FFileAccessCriticalSection.Release();
  631. end;
  632. end;
  633. function TLogSession.DoWriteToLog(const AString: string): Boolean;
  634. begin
  635. try
  636. FFileAccessCriticalSection.Enter;
  637. Append(FLogFile);
  638. WriteLn(FLogFile, AString);
  639. CloseFile(FLogFile);
  640. FFileAccessCriticalSection.Release();
  641. Result := True;
  642. except
  643. // Ignore exceptions.
  644. Result := False;
  645. FFileAccessCriticalSection.Release();
  646. end;
  647. end;
  648. procedure TLogSession.FlushBuffer;
  649. begin
  650. if Buffered then
  651. DoWriteBufferToLog();
  652. end;
  653. constructor TLogSession.Init(const AFileName: string;
  654. const ATimeFormat: TLogTimeFormat; const ALevels: TLogLevels;
  655. const ALogThreadId: Boolean = True; const ABuffered: Boolean = False;
  656. const AMaxSize: Integer = 0; const ABackUpOldLogs: Boolean = False;
  657. const AClearOldLogs: Boolean = True;
  658. const AWriteInternalMessages: Boolean = True);
  659. var
  660. i: Integer;
  661. ModeStr: string;
  662. begin
  663. FBuffer := TStringList.Create();
  664. FLogThreadId := ALogThreadId;
  665. FFlushBufferPeriod := 5000; // 5 sec.
  666. FCheckFileSizePeriod := 4000; // 4 sec.
  667. FBufferCriticalSection := TCriticalSection.Create;
  668. FFileAccessCriticalSection := TCriticalSection.Create;
  669. FBuffered := ABuffered; // Do not call the setter, create thread later.
  670. FStartedMs := GetTickCount;
  671. FTimeFormat := ATimeFormat;
  672. FLogLevels := ALevels;
  673. FMessageLimitAction := mlaHalt;
  674. FDisplayErrorDialogs := True;
  675. FDisplayLogOnExitIfItContains := [lkError, lkFatalError];
  676. FWriteInternalMessages := AWriteInternalMessages;
  677. // Set up strings.
  678. FModeTitles[lkDebug] := 'debug info';
  679. FModeTitles[lkInfo] := 'info';
  680. FModeTitles[lkNotice] := 'notices';
  681. FModeTitles[lkWarning] := 'warnings';
  682. FModeTitles[lkError] := 'errors';
  683. FModeTitles[lkFatalError] := 'fatal errors';
  684. case FTimeFormat of
  685. lfNone:
  686. ModeStr := 'no timestamp mode.';
  687. lfDate:
  688. ModeStr := 'date only mode.';
  689. lfTime:
  690. ModeStr := 'time only mode.';
  691. lfTimeExact:
  692. ModeStr := 'time mode with milliseconds.';
  693. lfDateTime:
  694. ModeStr := 'date and time mode.';
  695. lfElapsed:
  696. ModeStr := 'elapsed time mode.';
  697. end;
  698. if ABackUpOldLogs then
  699. BackUpOldLogs(AFileName);
  700. // Attach log file.
  701. FUsedLogFileNames := TStringList.Create();
  702. FOriginalLogFileName := AFileName;
  703. FEnabled := AttachLogFile(AFileName, AClearOldLogs);
  704. // Clear all logs and set log max size.
  705. if AMaxSize > 0 then
  706. ClearLogsInTheSameDir();
  707. Self.SetLogFileMaxSize(AMaxSize);
  708. // Reset log counters.
  709. for i := Ord(Low(TLogLevel)) to Ord(High(TLogLevel)) do
  710. FLogKindCount[TLogLevel(i)] := 0;
  711. // Print some initial logs.
  712. if FWriteInternalMessages then
  713. begin
  714. Log('Log subsystem started in ' + ModeStr, lkInfo);
  715. PrintLogLevels();
  716. Log('Buffered mode: ' + BoolToStr(FBuffered, True), lkInfo);
  717. end;
  718. // Start BufferProcessing thread.
  719. if FBuffered then
  720. ChangeBufferedState();
  721. end;
  722. {$IFNDEF USE_LOGGING}
  723. constructor TLogSession.OnlyCreate;
  724. begin
  725. inherited;
  726. end;
  727. {$ENDIF}
  728. procedure TLogSession.PrintLogLevels;
  729. var
  730. ModeStr: string;
  731. i: Integer;
  732. begin
  733. ModeStr := '[';
  734. for i := Ord(Low(TLogLevel)) to Ord(High(TLogLevel)) do
  735. if TLogLevel(i) in FLogLevels then
  736. begin
  737. if ModeStr <> '[' then
  738. ModeStr := ModeStr + ', ';
  739. ModeStr := ModeStr + FModeTitles[TLogLevel(i)] + ' ' +
  740. Trim(lkPrefix[TLogLevel(i)]);
  741. end;
  742. ModeStr := ModeStr + ']';
  743. if FLogLevels = [] then
  744. ModeStr := 'nothing';
  745. Log('Logging ' + ModeStr, lkInfo);
  746. end;
  747. procedure TLogSession.PrintLogStatistics;
  748. begin
  749. Log('Logged fatal_errors: ' + IntToStr(FLogKindCount[lkFatalError]) +
  750. ', errors: ' + IntToStr(FLogKindCount[lkError]) + ', warnings: ' +
  751. IntToStr(FLogKindCount[lkWarning]) + ', notices: ' +
  752. IntToStr(FLogKindCount[lkNotice]) + ', info: ' +
  753. IntToStr(FLogKindCount[lkInfo]) + ', debug: ' +
  754. IntToStr(FLogKindCount[lkDebug]));
  755. end;
  756. function TLogSession.AttachLogFile(const AFileName: string;
  757. const AResetFile: Boolean = True): Boolean;
  758. var
  759. lPath: string;
  760. begin
  761. try
  762. lPath := ExtractFilePath(AFileName);
  763. if Length(lPath) > 0 then
  764. begin
  765. FCurrentLogFileName := AFileName;
  766. ForceDirectories(lPath);
  767. end
  768. else
  769. FCurrentLogFileName := IncludeTrailingPathDelimiter(GetCurrentDir) +
  770. AFileName;
  771. FFileAccessCriticalSection.Enter;
  772. AssignFile(FLogFile, FCurrentLogFileName);
  773. FFileAccessCriticalSection.Leave;
  774. FUsedLogFileNames.Add(FCurrentLogFileName);
  775. if not FileExists(FCurrentLogFileName) then
  776. Result := DoResetLog()
  777. else
  778. begin
  779. if not AResetFile then
  780. Result := True
  781. else
  782. Result := DoResetLog();
  783. end;
  784. except
  785. FFileAccessCriticalSection.Leave;
  786. Result := False;
  787. end;
  788. end;
  789. procedure TLogSession.ChangeBufferedState();
  790. begin
  791. if (FBuffered) then
  792. begin
  793. FBufferProcessingThread := TLogBufferFlushThread.Create(Self);
  794. FBufferProcessingThread.Start();
  795. end
  796. else
  797. begin
  798. FBufferProcessingThread.Terminate();
  799. // Not really safe because we can wait forever.
  800. // But other methods known to me are platform-dependant.
  801. FBufferProcessingThread.WaitFor();
  802. FBufferProcessingThread.Free();
  803. end;
  804. end;
  805. procedure TLogSession.ClearLogsInTheSameDir;
  806. var
  807. sRec: TSearchRec;
  808. lFilePath: string;
  809. procedure DeleteCurrentFile();
  810. begin
  811. if FCurrentLogFileName <> lFilePath + sRec.Name then
  812. DeleteFile(lFilePath + sRec.Name);
  813. end;
  814. begin
  815. lFilePath := ExtractFilePath(FCurrentLogFileName);
  816. If FindFirst(lFilePath + RemovePathAndExt(FCurrentLogFileName) + '*' +
  817. ExtractFileExt(FCurrentLogFileName), faAnyFile, sRec) = 0 then
  818. begin
  819. try
  820. DeleteCurrentFile()
  821. except
  822. end;
  823. while (FindNext(sRec) = 0) do
  824. try
  825. DeleteCurrentFile();
  826. except
  827. end;
  828. FindClose(sRec);
  829. end;
  830. end;
  831. procedure TLogSession.CreateNewLogFileIfNeeded;
  832. var
  833. lNewFileName: string;
  834. i, Index: Integer;
  835. lFileSize: Integer;
  836. begin
  837. try
  838. FFileAccessCriticalSection.Enter;
  839. lFileSize := FileSize(FCurrentLogFileName);
  840. FFileAccessCriticalSection.Leave();
  841. except
  842. lFileSize := -1;
  843. FFileAccessCriticalSection.Leave();
  844. end;
  845. if lFileSize >= FLogFileMaxSize then
  846. begin
  847. i := 1;
  848. lNewFileName := FOriginalLogFileName;
  849. repeat
  850. Index := LastDelimiter('.', FOriginalLogFileName);
  851. if Index = -1 then
  852. exit;
  853. lNewFileName := FOriginalLogFileName;
  854. Insert('_' + IntToStr(i), lNewFileName, Index);
  855. inc(i);
  856. until not FileExists(lNewFileName);
  857. if FWriteInternalMessages then
  858. begin
  859. Log(Format
  860. ('Creating new log file "%s" because old one became too big (%d bytes)',
  861. [lNewFileName, lFileSize]));
  862. end;
  863. AttachLogFile(lNewFileName, True);
  864. end;
  865. end;
  866. destructor TLogSession.Destroy;
  867. var
  868. i: TLogLevel;
  869. begin
  870. FDestroying := True;
  871. {$IFNDEF USE_LOGGING}
  872. if Self = v_GLSLogger then
  873. exit;
  874. {$ENDIF}
  875. if FWriteInternalMessages then
  876. begin
  877. PrintLogStatistics();
  878. Log('Log session shutdown');
  879. end;
  880. SetBuffered(False);
  881. DoWriteBufferToLog(); // Terminates TLogBufferFlushThread.
  882. FBuffer.Free;
  883. SetLogFileMaxSize(0); // Terminates TLogCheckSizeThread.
  884. // Display log?
  885. for i := Low(TLogLevel) to High(TLogLevel) do
  886. if (i in FDisplayLogOnExitIfItContains) and (FLogKindCount[i] > 0) then
  887. begin
  888. DisplayLog();
  889. Break;
  890. end;
  891. if Self = v_GLSLogger then
  892. v_GLSLogger := nil;
  893. FUsedLogFileNames.Destroy;
  894. FBufferCriticalSection.Destroy;
  895. FFileAccessCriticalSection.Destroy;
  896. end;
  897. procedure TLogSession.DisplayLog;
  898. {$IF Defined(LINUX) and not Defined(CROSSVCL)}
  899. var
  900. lProcess: TProcess;
  901. {$ENDIF}
  902. begin
  903. {$IFDEF MSWINDOWS}
  904. ShellExecute(0, 'open', 'C:\WINDOWS\notepad.exe',
  905. PChar(FCurrentLogFileName), nil, 1);
  906. {$ENDIF}
  907. {$IF Defined(LINUX) and not Defined(CROSSVCL)}
  908. lProcess := TProcess.Create(nil);
  909. lProcess.CommandLine := 'gedit ' + FCurrentLogFileName;
  910. lProcess.Execute;
  911. lProcess.Destroy;
  912. {$ENDIF}
  913. end;
  914. procedure TLogSession.Log(const Desc: string; const Level: TLogLevel = lkInfo);
  915. begin
  916. AppendLog(Desc, Level);
  917. end;
  918. procedure TLogSession.LogAdv(const args: array of const;
  919. const ALevel: TLogLevel);
  920. begin
  921. Log(ConstArrayToString(args), ALevel);
  922. end;
  923. procedure TLogSession.LogDebug(const Desc: string);
  924. begin
  925. Log(Desc, lkDebug);
  926. end;
  927. procedure TLogSession.LogInfo(const Desc: string);
  928. begin
  929. Log(Desc, lkInfo);
  930. end;
  931. procedure TLogSession.LogNotice(const Desc: string);
  932. begin
  933. Log(Desc, lkNotice);
  934. end;
  935. procedure TLogSession.LogWarning(const Desc: string);
  936. begin
  937. Log(Desc, lkWarning);
  938. end;
  939. procedure TLogSession.LogEmtryLine;
  940. begin
  941. if not FEnabled then
  942. exit;
  943. {$IFNDEF USE_LOGGING}
  944. if Self = v_GLSLogger then
  945. exit;
  946. {$ENDIF}
  947. if FBuffered then
  948. begin
  949. // Critical section is always used.
  950. FBufferCriticalSection.Enter;
  951. FBuffer.Add('');
  952. FBufferCriticalSection.Leave;
  953. end
  954. else
  955. begin
  956. DoWriteToLog('');
  957. end;
  958. // IDELogProc.
  959. if (Self = v_GLSLogger) and Assigned(vIDELogProc) then
  960. vIDELogProc('');
  961. end;
  962. procedure TLogSession.LogError(const Desc: string);
  963. begin
  964. Log(Desc, lkError);
  965. end;
  966. procedure TLogSession.LogFatalError(const Desc: string);
  967. begin
  968. Log(Desc, lkFatalError);
  969. end;
  970. procedure TLogSession.LogDebugFmt(const Desc: string;
  971. const args: array of const);
  972. begin
  973. Log(Format(Desc, args), lkDebug);
  974. end;
  975. procedure TLogSession.LogInfoFmt(const Desc: string;
  976. const args: array of const);
  977. begin
  978. Log(Format(Desc, args), lkInfo);
  979. end;
  980. procedure TLogSession.LogNoticeFmt(const Desc: string;
  981. const args: array of const);
  982. begin
  983. Log(Format(Desc, args), lkWarning);
  984. end;
  985. procedure TLogSession.LogWarningFmt(const Desc: string;
  986. const args: array of const);
  987. begin
  988. Log(Format(Desc, args), lkWarning);
  989. end;
  990. procedure TLogSession.LogErrorFmt(const Desc: string;
  991. const args: array of const);
  992. begin
  993. Log(Format(Desc, args), lkError);
  994. end;
  995. procedure TLogSession.LogException(const E: Exception;
  996. const aFunctionName: string; const args: array of const;
  997. const ALevel: TLogLevel = lkError);
  998. begin
  999. Log('Exception in ' + aFunctionName + ': ' + E.Message + string(#13#10) +
  1000. 'Input parameters:' + string(#13#10) + ConstArrayToString(args), ALevel);
  1001. end;
  1002. procedure TLogSession.LogFatalErrorFmt(const Desc: string;
  1003. const args: array of const);
  1004. begin
  1005. Log(Format(Desc, args), lkFatalError);
  1006. end;
  1007. procedure TLogSession.AppendLog(const AString: string; const ALevel: TLogLevel;
  1008. const ALogTime: Boolean);
  1009. var
  1010. line: string;
  1011. begin
  1012. {$IFNDEF USE_LOGGING}
  1013. if Self = v_GLSLogger then
  1014. exit;
  1015. {$ENDIF}
  1016. if not(ALevel in LogLevels) or not FEnabled then
  1017. exit;
  1018. if ALogTime then
  1019. case FTimeFormat of
  1020. lfNone:
  1021. line := lkPrefix[ALevel] + AString;
  1022. lfDate:
  1023. line := DateToStr(Now) + #9 + lkPrefix[ALevel] + AString;
  1024. lfTime:
  1025. line := TimeToStr(Now) + #9 + lkPrefix[ALevel] + AString;
  1026. lfTimeExact:
  1027. line := FormatDateTime('hh:nn:ss zzz "ms"', Now) + #9 + lkPrefix[ALevel]
  1028. + AString;
  1029. lfDateTime:
  1030. line := DateTimeToStr(Now) + #9 + lkPrefix[ALevel] + AString;
  1031. lfElapsed:
  1032. line := IntToStr(GetTickCount - FStartedMs) + #9 + lkPrefix[ALevel]
  1033. + AString;
  1034. end
  1035. else
  1036. line := AString;
  1037. {$IFDEF USE_MULTITHREAD}
  1038. if (FLogThreadId) then
  1039. line := #9 + 'Thread ID ' + IntToStr(GetCurrentThreadId) + #9 + line;
  1040. {$ENDIF}
  1041. if FBuffered then
  1042. begin
  1043. // Critical section is always used.
  1044. FBufferCriticalSection.Enter;
  1045. FBuffer.Add(line);
  1046. FBufferCriticalSection.Leave;
  1047. end
  1048. else
  1049. begin
  1050. DoWriteToLog(line);
  1051. end;
  1052. // IDELogProc.
  1053. if (Self = v_GLSLogger) and Assigned(vIDELogProc) then
  1054. vIDELogProc('Scene: ' + line);
  1055. // Message limit?
  1056. inc(FLogKindCount[ALevel]);
  1057. if llMessageLimit[ALevel] < FLogKindCount[ALevel] then
  1058. case FMessageLimitAction of
  1059. mlaContinue: // Do nothing.
  1060. ;
  1061. mlaStopLogging:
  1062. begin
  1063. Log('Logging stopped due to reaching message limit (' + FModeTitles
  1064. [ALevel] + ' = ' + IntToStr(FLogKindCount[ALevel]) + ')');
  1065. FEnabled := False;
  1066. end;
  1067. mlaHalt:
  1068. begin
  1069. Log('Application halted due to reaching log message limit (' +
  1070. FModeTitles[ALevel] + ' = ' + IntToStr(FLogKindCount[ALevel]) + ')');
  1071. SetBuffered(False);
  1072. Halt;
  1073. end;
  1074. end;
  1075. end;
  1076. // TLogBufferFlushThread
  1077. constructor TLogBufferFlushThread.Create(const AParent: TLogSession);
  1078. begin
  1079. FParent := AParent;
  1080. inherited Create(True);
  1081. end;
  1082. procedure TLogBufferFlushThread.Execute;
  1083. begin
  1084. while (not Terminated) or (FParent.FBuffer.Count > 0) do
  1085. begin
  1086. FParent.DoWriteBufferToLog();
  1087. Sleep(FParent.FFlushBufferPeriod);
  1088. end;
  1089. end;
  1090. //-------------------------------
  1091. // TLogCheckSizeThread
  1092. //-------------------------------
  1093. constructor TLogCheckSizeThread.Create(const AParent: TLogSession);
  1094. begin
  1095. FParent := AParent;
  1096. inherited Create(True);
  1097. end;
  1098. procedure TLogCheckSizeThread.Execute;
  1099. begin
  1100. while (not Terminated and not FParent.FDestroying) do
  1101. begin
  1102. FParent.CreateNewLogFileIfNeeded();
  1103. Sleep(FParent.FCheckFileSizePeriod);
  1104. end;
  1105. end;
  1106. // -----------------------------------------------
  1107. initialization
  1108. // -----------------------------------------------
  1109. finalization
  1110. if (v_GLSLogger <> nil) then
  1111. v_GLSLogger.Destroy;
  1112. end.