GLS.Logger.pas 33 KB

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