GLSLog.pas 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. (*
  5. Activate USE_LOGGING in "GLSCene.inc" to turn on inner GLScene logger.
  6. You may have only one instance of TGLSLogger
  7. To obtain it, call UserLog() function from any unit.
  8. *)
  9. unit GLSLog;
  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 SkipBeforeSTR(var TextFile: Text; const SkipSTR: string): Boolean;
  237. function ReadLine(var TextFile: Text): string;
  238. (* Inner logger.
  239. Converted to a function, because in case of a DLL and main app using this module,
  240. log is written to the same file on initialization and finalization,
  241. which is not what one might want. This also allows to create a GLSLogger with
  242. custom parameters for user's application, for example a different log path
  243. (Often the EXE application directory is read-only).
  244. *)
  245. function GLSLogger(): TLogSession;
  246. procedure UseCustomGLSLogger(const ALogger: TLogSession);
  247. function ConstArrayToString(const Elements: array of const): String;
  248. var
  249. vIDELogProc: TIDELogProc;
  250. // --------------------------------------------------------------------------
  251. implementation
  252. // --------------------------------------------------------------------------
  253. var
  254. v_GLSLogger: TLogSession;
  255. vAssertErrorHandler: TAssertErrorProc;
  256. vCurrentLogger: TGLSLogger;
  257. // Inner logger. Create on first use, not in unit initialization. }
  258. function GLSLogger(): TLogSession;
  259. begin
  260. if v_GLSLogger = nil then
  261. begin
  262. {$IFDEF USE_LOGGING}
  263. v_GLSLogger := TLogSession.Init(Copy(ExtractFileName(ParamStr(0)), 1,
  264. Length(ExtractFileName(ParamStr(0))) - Length(ExtractFileExt(ParamStr(0)))
  265. ) + '.log', lfElapsed, llMax);
  266. {$ELSE}
  267. v_GLSLogger := TLogSession.OnlyCreate;
  268. {$ENDIF}
  269. end;
  270. Result := v_GLSLogger;
  271. end;
  272. procedure UseCustomGLSLogger(const ALogger: TLogSession);
  273. begin
  274. if (v_GLSLogger <> nil) then
  275. v_GLSLogger.Destroy;
  276. v_GLSLogger := ALogger;
  277. end;
  278. const
  279. // VarRec -> String
  280. vTypeDesc: Array [0 .. 16] of String = ('vtInteger', 'vtBoolean', 'vtChar',
  281. 'vtExtended', 'vtString', 'vtPointer', 'vtPChar', 'vtObject', 'vtClass',
  282. 'vtWideChar', 'vtPWideChar', 'vtAnsiString', 'vtCurrency', 'vtVariant',
  283. 'vtInterface', 'vtWideString', 'vtInt64');
  284. vTypeAsSring: Array [0 .. 17] of String = ('Integer : ', 'Boolean : ',
  285. 'Char : ', 'Extended : ', 'String : ', 'Pointer : ',
  286. 'PChar : ', 'TObject : ', 'Class : ', 'WideChar : ',
  287. 'PWideChar : ', 'AnsiString : ', 'Currency : ', 'Variant : ',
  288. 'Interface : ', 'WideString : ', 'Int64 : ', '#HLType : ');
  289. { Function from HotLog by Olivier Touzot "QnnO". }
  290. function GetOriginalValue(const s: String): String;
  291. // Called to remove the false 'AnsiString :' assertion, for pointers and objects
  292. begin
  293. Result := RightStr(s, Length(s) - 19);
  294. end;
  295. { Function from HotLog by Olivier Touzot "QnnO". }
  296. function VarRecToStr(const vr: TVarRec): String;
  297. // See D6PE help topic "TVarRec"
  298. begin
  299. Result := vTypeAsSring[vr.VType] + ' ';
  300. try
  301. with vr do
  302. case VType of
  303. vtInteger:
  304. Result := Result + IntToStr(VInteger);
  305. vtBoolean:
  306. Result := Result + BoolToStr(VBoolean, True);
  307. vtChar:
  308. Result := Result + string(VChar);
  309. vtExtended:
  310. Result := Result + FloatToStr(VExtended^);
  311. vtString:
  312. Result := Result + string(VString^);
  313. // maintened in case of future need, but will actually not arrive.
  314. vtPointer:
  315. Result := Result + '^(' + Format('%P', [(addr(VPointer))]) + ')';
  316. vtPChar:
  317. Result := Result + string(VPChar);
  318. // ...
  319. vtObject:
  320. begin
  321. if VObject = Nil Then
  322. Result := Result + '^(NIL)'
  323. else
  324. Result := Result + VObject.classname;
  325. end;
  326. // ...
  327. vtClass:
  328. Result := Result + VClass.classname;
  329. vtWideChar:
  330. Result := Result + string(VWideChar);
  331. vtPWideChar:
  332. Result := Result + VPWideChar;
  333. vtAnsiString:
  334. Result := Result + string(VAnsiString);
  335. vtCurrency:
  336. Result := Result + CurrToStr(VCurrency^);
  337. vtVariant:
  338. Result := Result + string(VVariant^);
  339. vtInterface:
  340. Result := Result + '(Interfaced object)';
  341. vtWideString:
  342. Result := Result + string(VWideString^);
  343. vtInt64:
  344. Result := Result + IntToStr(VInt64^);
  345. else
  346. Result := Result + Format('[#HLvrType(%d)]', // "Else" not possible...
  347. [Integer(vr.VType)]); // ...with D6, but laters ?
  348. end; { case }
  349. EXCEPT
  350. Result := Result + Format('[#HLvrValue(%s)]', [vTypeDesc[vr.VType]]);
  351. end;
  352. end;
  353. // Function from HotLog by Olivier Touzot "QnnO".
  354. function GetBasicValue(const s: String; vKind: Byte): String;
  355. var
  356. iTmp: Integer;
  357. wasTObject: Boolean;
  358. begin
  359. Result := s;
  360. If s = '' then
  361. exit;
  362. try
  363. iTmp := Pos('$_H_', s);
  364. wasTObject := (Pos('$_H_TObject', s) > 0);
  365. if (iTmp > 0) then
  366. Result := GetOriginalValue(s); // converts fake strings back to original
  367. Result := RightStr(Result, Length(Result) - 15);
  368. // From now on, works on "result"
  369. if (vKind In [vtString, vtAnsiString, vtWideString, vtPChar, vtWideChar,
  370. vtPWideChar]) And Not(wasTObject) then
  371. exit
  372. else
  373. begin
  374. iTmp := Pos(' ', Result);
  375. If (iTmp > 0) and (iTmp < Length(Result)) then
  376. Result := LeftStr(Result, iTmp);
  377. end;
  378. EXCEPT
  379. ;
  380. end;
  381. end;
  382. { Function from HotLog by Olivier Touzot "QnnO". }
  383. function ConstArrayToString(const Elements: array of const): String;
  384. // -2-> Returns à string, surrounded by parenthesis : '(elts[0]; ...; elts[n-1]);'
  385. // ("Basic infos" only.)
  386. Var
  387. i: Integer;
  388. s, sep: String;
  389. Begin
  390. TRY
  391. if Length(Elements) = 0 then
  392. begin
  393. Result := '';
  394. exit;
  395. end;
  396. Result := '(';
  397. sep := '; ';
  398. for i := Low(Elements) to High(Elements) do
  399. begin
  400. s := VarRecToStr(Elements[i]);
  401. Result := Result + GetBasicValue(s, Elements[i].VType) + sep;
  402. end;
  403. Result := LeftStr(Result, Length(Result) - 2) + ');';
  404. // replaces last ", " by final ");".
  405. except
  406. Result := '[#HLvrConvert]';
  407. eND;
  408. end;
  409. function UserLog: TLogSession;
  410. begin
  411. if Assigned(vCurrentLogger) then
  412. Result := vCurrentLogger.Log
  413. else
  414. Result := nil;
  415. end;
  416. function RemovePathAndExt(const AFileName: string): string;
  417. var
  418. lExtIndex: Integer;
  419. begin
  420. Result := ExtractFileName(AFileName);
  421. lExtIndex := Pos(ExtractFileExt(Result), Result);
  422. Result := Copy(Result, 1, lExtIndex - 1);
  423. end;
  424. procedure LogedAssert(const Message, FileName: string; LineNumber: Integer;
  425. ErrorAddr: Pointer);
  426. begin
  427. UserLog.Log(Message + ': in ' + FileName + ' at line ' +
  428. IntToStr(LineNumber), lkError);
  429. Abort;
  430. end;
  431. function FileSize(const AFileName: String): Integer;
  432. var
  433. sr: TSearchRec;
  434. begin
  435. if FindFirst(AFileName, faAnyFile, sr) = 0 then
  436. begin
  437. Result := sr.Size;
  438. FindClose(sr);
  439. end
  440. else
  441. Result := -1;
  442. end;
  443. function SkipBeforeSTR(var TextFile: Text; const SkipSTR: string): Boolean;
  444. var
  445. s: string;
  446. begin
  447. repeat
  448. readln(TextFile, s);
  449. if s = SkipSTR then
  450. begin
  451. Result := True;
  452. exit;
  453. end;
  454. until False;
  455. Result := False;
  456. end;
  457. function ReadLine(var TextFile: Text): string;
  458. var
  459. i: Word;
  460. var
  461. s: string;
  462. begin
  463. if EOF(TextFile) then
  464. exit;
  465. i := 1;
  466. repeat
  467. readln(TextFile, s);
  468. until (s <> '') and (s[1] <> '#') or EOF(TextFile);
  469. if s <> '' then
  470. begin
  471. while s[i] = ' ' do
  472. inc(i);
  473. if i = Length(s) then
  474. s := ''
  475. else
  476. s := Copy(s, i, Length(s) - i + 1);
  477. end;
  478. Result := s;
  479. end;
  480. // ------------------
  481. // ------------------ TGLSLogger ------------------
  482. // ------------------
  483. constructor TGLSLogger.Create(AOwner: TComponent);
  484. begin
  485. inherited Create(AOwner);
  486. FTimeFormat := lfElapsed;
  487. FLogLevels := llMax;
  488. vAssertErrorHandler := AssertErrorProc;
  489. vCurrentLogger := Self;
  490. end;
  491. destructor TGLSLogger.Destroy;
  492. begin
  493. if vCurrentLogger = Self then
  494. vCurrentLogger := nil;
  495. if Assigned(FLog) then
  496. FLog.Destroy;
  497. inherited Destroy;
  498. end;
  499. function TGLSLogger.GetLog: TLogSession;
  500. begin
  501. if not Assigned(FLog) then
  502. FLog := TLogSession.Init(Name + '.log', FTimeFormat, FLogLevels);
  503. Result := FLog;
  504. end;
  505. procedure TGLSLogger.DoPrimary;
  506. begin
  507. vCurrentLogger := Self;
  508. end;
  509. procedure TGLSLogger.SetReplaceAssertion(Value: Boolean);
  510. begin
  511. if Value <> FReplaceAssertion then
  512. begin
  513. FReplaceAssertion := Value;
  514. case FReplaceAssertion of
  515. True:
  516. AssertErrorProc := @LogedAssert;
  517. False:
  518. AssertErrorProc := @vAssertErrorHandler;
  519. end;
  520. end;
  521. end;
  522. // ------------------
  523. // ------------------ TLogSession ------------------
  524. // ------------------
  525. procedure TLogSession.BackUpOldLogs(const ACurrentLogFileName: string);
  526. var
  527. sRec: TSearchRec;
  528. lLogFileName: string;
  529. lLogOriginalDir: string;
  530. lLogSaveDir: string;
  531. lLogExt: string;
  532. procedure SaveCurrentFile();
  533. var
  534. lErrorMessage: string;
  535. lFile: File;
  536. begin
  537. if not FDisplayErrorDialogs then
  538. RenameFile(lLogOriginalDir + sRec.Name, lLogSaveDir + sRec.Name)
  539. else
  540. begin
  541. lErrorMessage := 'Renaming of "%s" failed with error : %d. Try again?';
  542. while not RenameFile(lLogOriginalDir + sRec.Name,
  543. lLogSaveDir + sRec.Name) do
  544. begin
  545. if MessageDlg(Format(lErrorMessage, [lLogOriginalDir + sRec.Name,
  546. GetLastError]), mtWarning, [mbNo], 0) = mrNo then
  547. Break;
  548. AssignFile(lFile, lLogOriginalDir + sRec.Name);
  549. CloseFile(lFile);
  550. end;
  551. end;
  552. end;
  553. begin
  554. lLogExt := ExtractFileExt(ACurrentLogFileName);
  555. lLogFileName := RemovePathAndExt(ACurrentLogFileName);
  556. lLogOriginalDir := ExtractFilePath(ACurrentLogFileName);
  557. lLogSaveDir := lLogOriginalDir + FormatDateTime('yyyy-mm-dd hh-nn-ss', Now);
  558. if not CreateDir(lLogSaveDir) then
  559. exit;
  560. lLogSaveDir := lLogSaveDir + PathDelim;
  561. If FindFirst(lLogOriginalDir + lLogFileName + '*' + lLogExt, faAnyFile,
  562. sRec) = 0 then
  563. begin
  564. try
  565. SaveCurrentFile();
  566. except
  567. end;
  568. while (FindNext(sRec) = 0) do
  569. try
  570. SaveCurrentFile();
  571. except
  572. end;
  573. FindClose(sRec);
  574. end;
  575. end;
  576. procedure TLogSession.SetBuffered(const Value: Boolean);
  577. begin
  578. if FBuffered = Value then
  579. exit;
  580. FBuffered := Value;
  581. ChangeBufferedState();
  582. end;
  583. procedure TLogSession.SetEnabled(const Value: Boolean);
  584. begin
  585. if (FEnabled = Value) then
  586. exit;
  587. FEnabled := Value;
  588. if (FEnabled) then
  589. Log('Logging session resumed')
  590. else
  591. Log('Logging session paused');
  592. end;
  593. procedure TLogSession.SetLogFileMaxSize(const Value: Integer);
  594. begin
  595. if FLogFileMaxSize = Value then
  596. exit;
  597. FLogFileMaxSize := Value;
  598. if FLogFileMaxSize > 0 then
  599. begin
  600. FCheckLogSizeThread := TLogCheckSizeThread.Create(Self);
  601. FCheckLogSizeThread.Start();
  602. end
  603. else
  604. begin
  605. FCheckLogSizeThread.Terminate();
  606. // DaStr: Not really safe because we can wait forever.
  607. // But other methods known to me are platform-dependant.
  608. FCheckLogSizeThread.WaitFor();
  609. FCheckLogSizeThread.Free();
  610. end;
  611. end;
  612. procedure TLogSession.SetMode(const NewMode: TLogLevels);
  613. begin
  614. {$IFNDEF USE_LOGGING}
  615. if Self = v_GLSLogger then
  616. exit;
  617. {$ENDIF}
  618. FLogLevels := NewMode;
  619. PrintLogLevels();
  620. end;
  621. function TLogSession.DoResetLog: Boolean;
  622. begin
  623. try
  624. FFileAccessCriticalSection.Enter;
  625. Rewrite(FLogFile);
  626. CloseFile(FLogFile);
  627. FFileAccessCriticalSection.Leave;
  628. Result := True;
  629. except
  630. on E: Exception do
  631. begin
  632. // Ignore exceptions.
  633. Result := False;
  634. FFileAccessCriticalSection.Leave;
  635. end;
  636. end;
  637. end;
  638. function TLogSession.DoWriteBufferToLog: Boolean;
  639. var
  640. i: Integer;
  641. lLast: Integer;
  642. begin
  643. try
  644. // Open file.
  645. FFileAccessCriticalSection.Enter;
  646. Append(FLogFile);
  647. // Write buffer.
  648. lLast := FBuffer.Count - 1;
  649. for i := 0 to lLast do
  650. WriteLn(FLogFile, FBuffer[i]);
  651. // Clear buffer.
  652. FBufferCriticalSection.Enter;
  653. FBuffer.Clear();
  654. FBufferCriticalSection.Leave;
  655. // Close file.
  656. CloseFile(FLogFile);
  657. FFileAccessCriticalSection.Release();
  658. Result := True;
  659. except
  660. // Ignore exceptions.
  661. Result := False;
  662. FFileAccessCriticalSection.Release();
  663. end;
  664. end;
  665. function TLogSession.DoWriteToLog(const AString: string): Boolean;
  666. begin
  667. try
  668. FFileAccessCriticalSection.Enter;
  669. Append(FLogFile);
  670. WriteLn(FLogFile, AString);
  671. CloseFile(FLogFile);
  672. FFileAccessCriticalSection.Release();
  673. Result := True;
  674. except
  675. // Ignore exceptions.
  676. Result := False;
  677. FFileAccessCriticalSection.Release();
  678. end;
  679. end;
  680. procedure TLogSession.FlushBuffer;
  681. begin
  682. if Buffered then
  683. DoWriteBufferToLog();
  684. end;
  685. constructor TLogSession.Init(const AFileName: string;
  686. const ATimeFormat: TLogTimeFormat; const ALevels: TLogLevels;
  687. const ALogThreadId: Boolean = True; const ABuffered: Boolean = False;
  688. const AMaxSize: Integer = 0; const ABackUpOldLogs: Boolean = False;
  689. const AClearOldLogs: Boolean = True;
  690. const AWriteInternalMessages: Boolean = True);
  691. var
  692. i: Integer;
  693. ModeStr: string;
  694. begin
  695. FBuffer := TStringList.Create();
  696. FLogThreadId := ALogThreadId;
  697. FFlushBufferPeriod := 5000; // 5 sec.
  698. FCheckFileSizePeriod := 4000; // 4 sec.
  699. FBufferCriticalSection := TCriticalSection.Create;
  700. FFileAccessCriticalSection := TCriticalSection.Create;
  701. FBuffered := ABuffered; // Do not call the setter, create thread later.
  702. FStartedMs := GetTickCount;
  703. FTimeFormat := ATimeFormat;
  704. FLogLevels := ALevels;
  705. FMessageLimitAction := mlaHalt;
  706. FDisplayErrorDialogs := True;
  707. FDisplayLogOnExitIfItContains := [lkError, lkFatalError];
  708. FWriteInternalMessages := AWriteInternalMessages;
  709. // Set up strings.
  710. FModeTitles[lkDebug] := 'debug info';
  711. FModeTitles[lkInfo] := 'info';
  712. FModeTitles[lkNotice] := 'notices';
  713. FModeTitles[lkWarning] := 'warnings';
  714. FModeTitles[lkError] := 'errors';
  715. FModeTitles[lkFatalError] := 'fatal errors';
  716. case FTimeFormat of
  717. lfNone:
  718. ModeStr := 'no timestamp mode.';
  719. lfDate:
  720. ModeStr := 'date only mode.';
  721. lfTime:
  722. ModeStr := 'time only mode.';
  723. lfTimeExact:
  724. ModeStr := 'time mode with milliseconds.';
  725. lfDateTime:
  726. ModeStr := 'date and time mode.';
  727. lfElapsed:
  728. ModeStr := 'elapsed time mode.';
  729. end;
  730. if ABackUpOldLogs then
  731. BackUpOldLogs(AFileName);
  732. // Attach log file.
  733. FUsedLogFileNames := TStringList.Create();
  734. FOriginalLogFileName := AFileName;
  735. FEnabled := AttachLogFile(AFileName, AClearOldLogs);
  736. // Clear all logs and set log max size.
  737. if AMaxSize > 0 then
  738. ClearLogsInTheSameDir();
  739. Self.SetLogFileMaxSize(AMaxSize);
  740. // Reset log counters.
  741. for i := Ord(Low(TLogLevel)) to Ord(High(TLogLevel)) do
  742. FLogKindCount[TLogLevel(i)] := 0;
  743. // Print some initial logs.
  744. if FWriteInternalMessages then
  745. begin
  746. Log('Log subsystem started in ' + ModeStr, lkInfo);
  747. PrintLogLevels();
  748. Log('Buffered mode: ' + BoolToStr(FBuffered, True), lkInfo);
  749. end;
  750. // Start BufferProcessing thread.
  751. if FBuffered then
  752. ChangeBufferedState();
  753. end;
  754. {$IFNDEF USE_LOGGING}
  755. constructor TLogSession.OnlyCreate;
  756. begin
  757. inherited;
  758. end;
  759. {$ENDIF}
  760. procedure TLogSession.PrintLogLevels;
  761. var
  762. ModeStr: string;
  763. i: Integer;
  764. begin
  765. ModeStr := '[';
  766. for i := Ord(Low(TLogLevel)) to Ord(High(TLogLevel)) do
  767. if TLogLevel(i) in FLogLevels then
  768. begin
  769. if ModeStr <> '[' then
  770. ModeStr := ModeStr + ', ';
  771. ModeStr := ModeStr + FModeTitles[TLogLevel(i)] + ' ' +
  772. Trim(lkPrefix[TLogLevel(i)]);
  773. end;
  774. ModeStr := ModeStr + ']';
  775. if FLogLevels = [] then
  776. ModeStr := 'nothing';
  777. Log('Logging ' + ModeStr, lkInfo);
  778. end;
  779. procedure TLogSession.PrintLogStatistics;
  780. begin
  781. Log('Logged fatal_errors: ' + IntToStr(FLogKindCount[lkFatalError]) +
  782. ', errors: ' + IntToStr(FLogKindCount[lkError]) + ', warnings: ' +
  783. IntToStr(FLogKindCount[lkWarning]) + ', notices: ' +
  784. IntToStr(FLogKindCount[lkNotice]) + ', info: ' +
  785. IntToStr(FLogKindCount[lkInfo]) + ', debug: ' +
  786. IntToStr(FLogKindCount[lkDebug]));
  787. end;
  788. function TLogSession.AttachLogFile(const AFileName: string;
  789. const AResetFile: Boolean = True): Boolean;
  790. var
  791. lPath: string;
  792. begin
  793. try
  794. lPath := ExtractFilePath(AFileName);
  795. if Length(lPath) > 0 then
  796. begin
  797. FCurrentLogFileName := AFileName;
  798. ForceDirectories(lPath);
  799. end
  800. else
  801. FCurrentLogFileName := IncludeTrailingPathDelimiter(GetCurrentDir) +
  802. AFileName;
  803. FFileAccessCriticalSection.Enter;
  804. AssignFile(FLogFile, FCurrentLogFileName);
  805. FFileAccessCriticalSection.Leave;
  806. FUsedLogFileNames.Add(FCurrentLogFileName);
  807. if not FileExists(FCurrentLogFileName) then
  808. Result := DoResetLog()
  809. else
  810. begin
  811. if not AResetFile then
  812. Result := True
  813. else
  814. Result := DoResetLog();
  815. end;
  816. except
  817. FFileAccessCriticalSection.Leave;
  818. Result := False;
  819. end;
  820. end;
  821. procedure TLogSession.ChangeBufferedState();
  822. begin
  823. if (FBuffered) then
  824. begin
  825. FBufferProcessingThread := TLogBufferFlushThread.Create(Self);
  826. FBufferProcessingThread.Start();
  827. end
  828. else
  829. begin
  830. FBufferProcessingThread.Terminate();
  831. // DaStr: Not really safe because we can wait forever.
  832. // But other methods known to me are platform-dependant.
  833. FBufferProcessingThread.WaitFor();
  834. FBufferProcessingThread.Free();
  835. end;
  836. end;
  837. procedure TLogSession.ClearLogsInTheSameDir;
  838. var
  839. sRec: TSearchRec;
  840. lFilePath: string;
  841. procedure DeleteCurrentFile();
  842. begin
  843. if FCurrentLogFileName <> lFilePath + sRec.Name then
  844. DeleteFile(lFilePath + sRec.Name);
  845. end;
  846. begin
  847. lFilePath := ExtractFilePath(FCurrentLogFileName);
  848. If FindFirst(lFilePath + RemovePathAndExt(FCurrentLogFileName) + '*' +
  849. ExtractFileExt(FCurrentLogFileName), faAnyFile, sRec) = 0 then
  850. begin
  851. try
  852. DeleteCurrentFile()
  853. except
  854. end;
  855. while (FindNext(sRec) = 0) do
  856. try
  857. DeleteCurrentFile();
  858. except
  859. end;
  860. FindClose(sRec);
  861. end;
  862. end;
  863. procedure TLogSession.CreateNewLogFileIfNeeded;
  864. var
  865. lNewFileName: string;
  866. i, Index: Integer;
  867. lFileSize: Integer;
  868. begin
  869. try
  870. FFileAccessCriticalSection.Enter;
  871. lFileSize := FileSize(FCurrentLogFileName);
  872. FFileAccessCriticalSection.Leave();
  873. except
  874. lFileSize := -1;
  875. FFileAccessCriticalSection.Leave();
  876. end;
  877. if lFileSize >= FLogFileMaxSize then
  878. begin
  879. i := 1;
  880. lNewFileName := FOriginalLogFileName;
  881. repeat
  882. Index := LastDelimiter('.', FOriginalLogFileName);
  883. if Index = -1 then
  884. exit;
  885. lNewFileName := FOriginalLogFileName;
  886. Insert('_' + IntToStr(i), lNewFileName, Index);
  887. inc(i);
  888. until not FileExists(lNewFileName);
  889. if FWriteInternalMessages then
  890. begin
  891. Log(Format
  892. ('Creating new log file "%s" because old one became too big (%d bytes)',
  893. [lNewFileName, lFileSize]));
  894. end;
  895. AttachLogFile(lNewFileName, True);
  896. end;
  897. end;
  898. destructor TLogSession.Destroy;
  899. var
  900. i: TLogLevel;
  901. begin
  902. FDestroying := True;
  903. {$IFNDEF USE_LOGGING}
  904. if Self = v_GLSLogger then
  905. exit;
  906. {$ENDIF}
  907. if FWriteInternalMessages then
  908. begin
  909. PrintLogStatistics();
  910. Log('Log session shutdown');
  911. end;
  912. SetBuffered(False);
  913. DoWriteBufferToLog(); // Terminates TLogBufferFlushThread.
  914. FBuffer.Free;
  915. SetLogFileMaxSize(0); // Terminates TLogCheckSizeThread.
  916. // Display log?
  917. for i := Low(TLogLevel) to High(TLogLevel) do
  918. if (i in FDisplayLogOnExitIfItContains) and (FLogKindCount[i] > 0) then
  919. begin
  920. DisplayLog();
  921. Break;
  922. end;
  923. if Self = v_GLSLogger then
  924. v_GLSLogger := nil;
  925. FUsedLogFileNames.Destroy;
  926. FBufferCriticalSection.Destroy;
  927. FFileAccessCriticalSection.Destroy;
  928. end;
  929. procedure TLogSession.DisplayLog;
  930. {$IFDEF LINUX}
  931. var
  932. lProcess: TProcess;
  933. {$ENDIF}
  934. begin
  935. {$IFDEF MSWINDOWS}
  936. ShellExecute(0, 'open', 'C:\WINDOWS\notepad.exe',
  937. PChar(FCurrentLogFileName), nil, 1);
  938. {$ENDIF}
  939. {$IFDEF LINUX}
  940. lProcess := TProcess.Create(nil);
  941. lProcess.CommandLine := 'gedit ' + FCurrentLogFileName;
  942. lProcess.Execute;
  943. lProcess.Destroy;
  944. {$ENDIF}
  945. end;
  946. procedure TLogSession.Log(const Desc: string; const Level: TLogLevel = lkInfo);
  947. begin
  948. AppendLog(Desc, Level);
  949. end;
  950. procedure TLogSession.LogAdv(const args: array of const;
  951. const ALevel: TLogLevel);
  952. begin
  953. Log(ConstArrayToString(args), ALevel);
  954. end;
  955. procedure TLogSession.LogDebug(const Desc: string);
  956. begin
  957. Log(Desc, lkDebug);
  958. end;
  959. procedure TLogSession.LogInfo(const Desc: string);
  960. begin
  961. Log(Desc, lkInfo);
  962. end;
  963. procedure TLogSession.LogNotice(const Desc: string);
  964. begin
  965. Log(Desc, lkNotice);
  966. end;
  967. procedure TLogSession.LogWarning(const Desc: string);
  968. begin
  969. Log(Desc, lkWarning);
  970. end;
  971. procedure TLogSession.LogEmtryLine;
  972. begin
  973. if not FEnabled then
  974. exit;
  975. {$IFNDEF USE_LOGGING}
  976. if Self = v_GLSLogger then
  977. exit;
  978. {$ENDIF}
  979. if FBuffered then
  980. begin
  981. // Critical section is always used.
  982. FBufferCriticalSection.Enter;
  983. FBuffer.Add('');
  984. FBufferCriticalSection.Leave;
  985. end
  986. else
  987. begin
  988. DoWriteToLog('');
  989. end;
  990. // IDELogProc.
  991. if (Self = v_GLSLogger) and Assigned(vIDELogProc) then
  992. vIDELogProc('');
  993. end;
  994. procedure TLogSession.LogError(const Desc: string);
  995. begin
  996. Log(Desc, lkError);
  997. end;
  998. procedure TLogSession.LogFatalError(const Desc: string);
  999. begin
  1000. Log(Desc, lkFatalError);
  1001. end;
  1002. procedure TLogSession.LogDebugFmt(const Desc: string;
  1003. const args: array of const);
  1004. begin
  1005. Log(Format(Desc, args), lkDebug);
  1006. end;
  1007. procedure TLogSession.LogInfoFmt(const Desc: string;
  1008. const args: array of const);
  1009. begin
  1010. Log(Format(Desc, args), lkInfo);
  1011. end;
  1012. procedure TLogSession.LogNoticeFmt(const Desc: string;
  1013. const args: array of const);
  1014. begin
  1015. Log(Format(Desc, args), lkWarning);
  1016. end;
  1017. procedure TLogSession.LogWarningFmt(const Desc: string;
  1018. const args: array of const);
  1019. begin
  1020. Log(Format(Desc, args), lkWarning);
  1021. end;
  1022. procedure TLogSession.LogErrorFmt(const Desc: string;
  1023. const args: array of const);
  1024. begin
  1025. Log(Format(Desc, args), lkError);
  1026. end;
  1027. procedure TLogSession.LogException(const E: Exception;
  1028. const aFunctionName: string; const args: array of const;
  1029. const ALevel: TLogLevel = lkError);
  1030. begin
  1031. Log('Exception in ' + aFunctionName + ': ' + E.Message + string(#13#10) +
  1032. 'Input parameters:' + string(#13#10) + ConstArrayToString(args), ALevel);
  1033. end;
  1034. procedure TLogSession.LogFatalErrorFmt(const Desc: string;
  1035. const args: array of const);
  1036. begin
  1037. Log(Format(Desc, args), lkFatalError);
  1038. end;
  1039. procedure TLogSession.AppendLog(const AString: string; const ALevel: TLogLevel;
  1040. const ALogTime: Boolean);
  1041. var
  1042. line: string;
  1043. begin
  1044. {$IFNDEF USE_LOGGING}
  1045. if Self = v_GLSLogger then
  1046. exit;
  1047. {$ENDIF}
  1048. if not(ALevel in LogLevels) or not FEnabled then
  1049. exit;
  1050. if ALogTime then
  1051. case FTimeFormat of
  1052. lfNone:
  1053. line := lkPrefix[ALevel] + AString;
  1054. lfDate:
  1055. line := DateToStr(Now) + #9 + lkPrefix[ALevel] + AString;
  1056. lfTime:
  1057. line := TimeToStr(Now) + #9 + lkPrefix[ALevel] + AString;
  1058. lfTimeExact:
  1059. line := FormatDateTime('hh:nn:ss zzz "ms"', Now) + #9 + lkPrefix[ALevel]
  1060. + AString;
  1061. lfDateTime:
  1062. line := DateTimeToStr(Now) + #9 + lkPrefix[ALevel] + AString;
  1063. lfElapsed:
  1064. line := IntToStr(GetTickCount - FStartedMs) + #9 + lkPrefix[ALevel]
  1065. + AString;
  1066. end
  1067. else
  1068. line := AString;
  1069. {$IFDEF USE_MULTITHREAD}
  1070. if (FLogThreadId) then
  1071. line := #9 + 'Thread ID ' + IntToStr(GetCurrentThreadId) + #9 + line;
  1072. {$ENDIF}
  1073. if FBuffered then
  1074. begin
  1075. // Critical section is always used.
  1076. FBufferCriticalSection.Enter;
  1077. FBuffer.Add(line);
  1078. FBufferCriticalSection.Leave;
  1079. end
  1080. else
  1081. begin
  1082. DoWriteToLog(line);
  1083. end;
  1084. // IDELogProc.
  1085. if (Self = v_GLSLogger) and Assigned(vIDELogProc) then
  1086. vIDELogProc('GLScene: ' + line);
  1087. // Message limit?
  1088. inc(FLogKindCount[ALevel]);
  1089. if llMessageLimit[ALevel] < FLogKindCount[ALevel] then
  1090. case FMessageLimitAction of
  1091. mlaContinue: { Do nothing. }
  1092. ;
  1093. mlaStopLogging:
  1094. begin
  1095. Log('Logging stopped due to reaching message limit (' + FModeTitles
  1096. [ALevel] + ' = ' + IntToStr(FLogKindCount[ALevel]) + ')');
  1097. FEnabled := False;
  1098. end;
  1099. mlaHalt:
  1100. begin
  1101. Log('Application halted due to reaching log message limit (' +
  1102. FModeTitles[ALevel] + ' = ' +
  1103. IntToStr(FLogKindCount[ALevel]) + ')');
  1104. SetBuffered(False);
  1105. Halt;
  1106. end;
  1107. end;
  1108. end;
  1109. // TLogBufferFlushThread
  1110. constructor TLogBufferFlushThread.Create(const AParent: TLogSession);
  1111. begin
  1112. FParent := AParent;
  1113. inherited Create(True);
  1114. end;
  1115. procedure TLogBufferFlushThread.Execute;
  1116. begin
  1117. while (not Terminated) or (FParent.FBuffer.Count > 0) do
  1118. begin
  1119. FParent.DoWriteBufferToLog();
  1120. Sleep(FParent.FFlushBufferPeriod);
  1121. end;
  1122. end;
  1123. // TLogCheckSizeThread
  1124. constructor TLogCheckSizeThread.Create(const AParent: TLogSession);
  1125. begin
  1126. FParent := AParent;
  1127. inherited Create(True);
  1128. end;
  1129. procedure TLogCheckSizeThread.Execute;
  1130. begin
  1131. while (not Terminated and not FParent.FDestroying) do
  1132. begin
  1133. FParent.CreateNewLogFileIfNeeded();
  1134. Sleep(FParent.FCheckFileSizePeriod);
  1135. end;
  1136. end;
  1137. // -----------------------------------------------
  1138. initialization
  1139. // -----------------------------------------------
  1140. finalization
  1141. if (v_GLSLogger <> nil) then
  1142. v_GLSLogger.Destroy;
  1143. end.