Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

463 linhas
15 KiB

  1. unit uutlLogger;
  2. { Package: Utils
  3. Prefix: utl - UTiLs
  4. Beschreibung: diese Unit enthält das Logging-Framework
  5. Anzusprechen über Singleton: utlLogger
  6. Die einzelnen Level sind über die Methoden Debug(), Info(), Warning(), Error() zugänglich.
  7. Sender: entweder eigener Text oder TObject-Referenz, dann wird Klassenname und Adresse ausgegeben.
  8. Log-Zeilen werden nicht weiter behandelt, sondern an Consumer verteilt.
  9. Es können beliebig viele Consumer per RegisterConsumer für bestimmte Level registriert werden.
  10. Jeder davon bekommt die Rohdaten eines Logeintrags auf einer beobachteten Stufe.
  11. Zum einfacheren Ausgeben gibt es eine Hilfsfunktion FormatLine vom Logger.
  12. Vordefinierte Consumer:
  13. TutlFileLogger - schreibt in eine Datei
  14. TutlConsoleLogger - schreibt auf die Konsole (ggf. mit CriticalSection)
  15. TutlEventLogger - ruft beliebiges Event auf
  16. }
  17. {$mode objfpc}{$H+}
  18. interface
  19. uses
  20. {$IFDEF MSWINDOWS}Windows{$ELSE}unix{$ENDIF},
  21. Classes, SysUtils, uutlGenerics, syncobjs, uutlCommon;
  22. type
  23. TutlLogLevel = (llDebug, llInfo, llWarning, llError);
  24. TutlLogLevels = set of TutlLogLevel;
  25. const
  26. utlLogLevel_Any = [low(TutlLogLevel)..high(TutlLogLevel)];
  27. utlLogLevel_NoDebug = utlLogLevel_Any - [llDebug];
  28. utlLogLevelStrings: array[TutlLogLevel] of string =
  29. ('Debug','Info','Warning','Error');
  30. type
  31. TutlLogger = class;
  32. IutlLogConsumer = interface(IUnknown)
  33. procedure WriteLog(const aLogger: TutlLogger; const aTime:TDateTime; const aLevel:TutlLogLevel; const aSender: string; const aMessage: String);
  34. end;
  35. TutlLogConsumerList = specialize TutlInterfaceList<IutlLogConsumer>;
  36. { TutlLogger }
  37. TutlLogger = class(TObject)
  38. private
  39. fConsumersLock: TCriticalSection;
  40. fConsumers: array[TutlLogLevel] of TutlLogConsumerList;
  41. protected
  42. class function FormatTime(const aTime:TDateTime): string;
  43. function FormatSender(const aSender: TObject): String;
  44. procedure InternalLog(const aLevel:TutlLogLevel; const aSender: TObject; const aMessage: String; const aParams: array of const); overload;
  45. procedure InternalLog(const aLevel:TutlLogLevel; const aSender: String; const aMessage: String; const aParams: array of const); overload;
  46. public
  47. procedure RegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter:TutlLogLevels=utlLogLevel_Any);
  48. procedure UnRegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter:TutlLogLevels=utlLogLevel_Any);
  49. class function FormatLine(const aTime:TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String): string;
  50. procedure Debug(const aSender: TObject; const aMessage: String; const aParams: array of const); overload;
  51. procedure Debug(const aSender: String; const aMessage: String; const aParams: array of const); overload;
  52. procedure Log(const aSender: TObject; const aMessage: String; const aParams: array of const); overload;
  53. procedure Log(const aSender: String; const aMessage: String; const aParams: array of const); overload;
  54. procedure Warning(const aSender: TObject; const aMessage: String; const aParams: array of const); overload;
  55. procedure Warning(const aSender: String; const aMessage: String; const aParams: array of const); overload;
  56. procedure Error(const aSender: TObject; const aMessage: String; const aParams: array of const); overload;
  57. procedure Error(const aSender: String; const aMessage: String; const aParams: array of const); overload;
  58. procedure Error(const aSender: String; const aMessage: String; const aException: Exception); overload;
  59. procedure Error(const aSender: TObject; const aMessage: String; const aException: Exception); overload;
  60. constructor Create;
  61. destructor Destroy; override;
  62. end;
  63. { TutlFileLogger }
  64. TutlFileLoggerMode = (flmCreateNew, flmAppend);
  65. TutlFileLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer)
  66. private
  67. fStream: TFileStream;
  68. fAutoFlush: Boolean;
  69. fAutoFree: Boolean;
  70. procedure SetAutoFree(aValue: Boolean);
  71. protected
  72. function _Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; override;
  73. protected
  74. procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  75. public
  76. constructor Create(const aFilename: String; const aMode: TutlFileLoggerMode; const aAutoFree: Boolean = false);
  77. destructor Destroy; override;
  78. procedure Flush(); overload;
  79. published
  80. property AutoFlush: Boolean read fAutoFlush write fAutoFlush;
  81. property AutoFree: Boolean read fAutoFree write SetAutoFree;
  82. end;
  83. { TutlConsoleLogger }
  84. TutlConsoleLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer)
  85. private
  86. fFreeConsoleCS: boolean;
  87. fConsoleCS: TCriticalSection;
  88. fOnBeforeLog: TNotifyEvent;
  89. fOnAfterLog: TNotifyEvent;
  90. protected
  91. procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); virtual;
  92. public
  93. property ConsoleCS: TCriticalSection read fConsoleCS;
  94. property OnBeforeLog: TNotifyEvent read fOnBeforeLog write fOnBeforeLog;
  95. property OnAfterLog: TNotifyEvent read fOnAfterLog write fOnAfterLog;
  96. constructor Create(const aSection: TCriticalSection = nil);
  97. destructor Destroy; override;
  98. end;
  99. { TutlEventLogger }
  100. TutlWriteLogEvent = procedure (const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String) of object;
  101. TutlEventLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer)
  102. private
  103. fWriteLogEvt: TutlWriteLogEvent;
  104. protected
  105. procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  106. public
  107. constructor Create(const aEvent: TutlWriteLogEvent);
  108. end;
  109. function utlLogger: TutlLogger;
  110. function utlCreateStackTrace(const aMessage: String; const aException: Exception): String;
  111. implementation
  112. var
  113. utlLogger_Singleton: TutlLogger;
  114. function utlLogger: TutlLogger;
  115. begin
  116. if not Assigned(utlLogger_Singleton) then
  117. utlLogger_Singleton:= TutlLogger.Create;
  118. Result:= utlLogger_Singleton;
  119. end;
  120. function utlCreateStackTrace(const aMessage: String; const aException: Exception): String;
  121. var
  122. i: Integer;
  123. frames: PPointer;
  124. begin
  125. result := aMessage;
  126. if Assigned(aException) then
  127. result := result + sLineBreak +
  128. ' Exception: ' + aException.ClassName + sLineBreak +
  129. ' Message: ' + aException.Message + sLineBreak +
  130. ' Thread: ' + IntToStr(GetCurrentThreadId) + sLineBreak +
  131. ' StackTrace:' + sLineBreak +
  132. ' ' + BackTraceStrFunc(ExceptAddr)
  133. else
  134. result := result + 'no Exception passed';
  135. frames := ExceptFrames;
  136. for i := 0 to ExceptFrameCount-1 do
  137. result := result + sLineBreak + ' ' + BackTraceStrFunc(frames[i]);
  138. end;
  139. { TutlFileLogger }
  140. function FileFlush(Handle: THandle): Boolean;
  141. begin
  142. {$IFDEF MSWINDOWS}
  143. Result:= FlushFileBuffers(Handle);
  144. {$ELSE}
  145. Result:= (fpfsync(Handle) = 0);
  146. {$ENDIF}
  147. end;
  148. procedure TutlFileLogger.SetAutoFree(aValue: Boolean);
  149. begin
  150. if fAutoFree = aValue then
  151. Exit;
  152. fAutoFree := aValue;
  153. if (fRefCount <= 0) and fAutoFree then
  154. Free;
  155. end;
  156. function TutlFileLogger._Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  157. begin
  158. result := inherited _Release;
  159. if (Result <= 0) and fAutoFree then
  160. Free;
  161. end;
  162. procedure TutlFileLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime;
  163. const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  164. var
  165. buf: AnsiString;
  166. begin
  167. if Assigned(fStream) then begin
  168. buf:= aLogger.FormatLine(aTime, aLevel, aSender, aMessage)+sLineBreak;
  169. fStream.Write(buf[1], Length(buf));
  170. if AutoFlush then
  171. FileFlush(fStream.Handle);
  172. end;
  173. end;
  174. constructor TutlFileLogger.Create(const aFilename: String; const aMode: TutlFileLoggerMode; const aAutoFree: Boolean = false);
  175. const
  176. RIGHTS: Cardinal = {$IFNDEF UNIX}fmShareDenyWrite{$ELSE}%0100100100 {-r--r--r--}{$ENDIF};
  177. begin
  178. try
  179. if (aMode = flmCreateNew) or not FileExists(aFilename) then begin
  180. if FileExists(aFilename) then
  181. DeleteFile(aFilename);
  182. fStream := TFileStream.Create(aFilename, fmCreate{$IFNDEF UNIX} or RIGHTS{$ENDIF}, RIGHTS)
  183. end else
  184. fStream := TFileStream.Create(aFilename, fmOpenReadWrite{$IFNDEF UNIX} or RIGHTS{$ENDIF}, RIGHTS);
  185. fStream.Position := fStream.Size;
  186. except
  187. on e: EStreamError do begin
  188. fStream:= nil;
  189. utlLogger.Error('Logger', 'Could not open log file "%s"',[aFilename]);
  190. end else
  191. raise;
  192. end;
  193. AutoFlush := true;
  194. fAutoFree := aAutoFree;
  195. end;
  196. destructor TutlFileLogger.Destroy;
  197. begin
  198. FreeAndNil(fStream);
  199. inherited Destroy;
  200. end;
  201. procedure TutlFileLogger.Flush;
  202. begin
  203. if Assigned(fStream) then
  204. FileFlush(fStream.Handle);
  205. end;
  206. { TutlConsoleLogger }
  207. procedure TutlConsoleLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime;
  208. const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  209. begin
  210. fConsoleCS.Acquire;
  211. try
  212. if Assigned(fOnBeforeLog) then
  213. fOnBeforeLog(Self);
  214. WriteLn(aLogger.FormatLine(aTime, aLevel, aSender, aMessage));
  215. if Assigned(fOnAfterLog) then
  216. fOnAfterLog(Self);
  217. finally
  218. fConsoleCS.Release;
  219. end;
  220. end;
  221. constructor TutlConsoleLogger.Create(const aSection: TCriticalSection);
  222. begin
  223. inherited Create;
  224. if Assigned(aSection) then
  225. fConsoleCS:= aSection
  226. else
  227. fConsoleCS:= TCriticalSection.Create;
  228. fFreeConsoleCS:= not Assigned(aSection);
  229. end;
  230. destructor TutlConsoleLogger.Destroy;
  231. begin
  232. if fFreeConsoleCS then
  233. FreeAndNil(fConsoleCS);
  234. inherited Destroy;
  235. end;
  236. { TutlEventLogger }
  237. procedure TutlEventLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime;
  238. const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  239. begin
  240. fWriteLogEvt(aLogger,aTime, aLevel, aSender, aMessage);
  241. end;
  242. constructor TutlEventLogger.Create(const aEvent: TutlWriteLogEvent);
  243. begin
  244. inherited Create;
  245. fWriteLogEvt:= aEvent;
  246. end;
  247. { TutlLogger }
  248. procedure TutlLogger.RegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter: TutlLogLevels);
  249. var
  250. ll: TutlLogLevel;
  251. tmp: IutlLogConsumer;
  252. begin
  253. fConsumersLock.Acquire;
  254. try
  255. tmp := aConsumer; // HACK: store interface due to ref count bug :/
  256. for ll:= low(ll) to high(ll) do
  257. if (ll in aFilter) and (fConsumers[ll].IndexOf(tmp) < 0) then
  258. fConsumers[ll].Add(tmp);
  259. finally
  260. tmp := nil;
  261. fConsumersLock.Release;
  262. end;
  263. if llDebug in aFilter then
  264. aConsumer.WriteLog(Self, Now, llDebug, 'System', 'Attached to Logger');
  265. end;
  266. procedure TutlLogger.UnRegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter: TutlLogLevels);
  267. var
  268. ll: TutlLogLevel;
  269. begin
  270. fConsumersLock.Acquire;
  271. try
  272. for ll := low(ll) to high(ll) do
  273. if ll in aFilter then
  274. fConsumers[ll].Remove(aConsumer);
  275. finally
  276. fConsumersLock.Release;
  277. end;
  278. end;
  279. class function TutlLogger.FormatTime(const aTime: TDateTime): string;
  280. begin
  281. Result:= FormatDateTime('hh:nn:ss.zzz',aTime);
  282. end;
  283. function TutlLogger.FormatSender(const aSender: TObject): String;
  284. begin
  285. if Assigned(aSender) then
  286. result := format('%s[0x%P]', [aSender.ClassName, Pointer(aSender)])
  287. else
  288. result := '';
  289. end;
  290. class function TutlLogger.FormatLine(const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String): string;
  291. begin
  292. if (aSender <> '') then
  293. Result:= Format('%s %-9s %s: %s', [FormatTime(aTime), UpperCase(utlLogLevelStrings[aLevel]), aSender, aMessage])
  294. else
  295. Result:= Format('%s %-9s %s', [FormatTime(aTime), UpperCase(utlLogLevelStrings[aLevel]), aMessage]);
  296. end;
  297. procedure TutlLogger.InternalLog(const aLevel: TutlLogLevel; const aSender: TObject; const aMessage: String; const aParams: array of const);
  298. begin
  299. InternalLog(aLevel, FormatSender(aSender), aMessage, aParams);
  300. end;
  301. procedure TutlLogger.InternalLog(const aLevel: TutlLogLevel; const aSender: String; const aMessage: String; const aParams: array of const);
  302. var
  303. msg: string;
  304. when: TDateTime;
  305. i: integer;
  306. begin
  307. if length(aParams) = 0 then
  308. msg:= aMessage
  309. else
  310. msg:= Format(aMessage, aParams);
  311. when:= Now;
  312. fConsumersLock.Acquire;
  313. try
  314. for i:= 0 to fConsumers[aLevel].Count-1 do begin
  315. fConsumers[aLevel][i].WriteLog(Self, when, aLevel, aSender, msg);
  316. end;
  317. finally
  318. fConsumersLock.Release;
  319. end;
  320. end;
  321. procedure TutlLogger.Debug(const aSender: TObject; const aMessage: String; const aParams: array of const);
  322. begin
  323. InternalLog(llDebug, aSender, aMessage, aParams);
  324. end;
  325. procedure TutlLogger.Debug(const aSender: String; const aMessage: String; const aParams: array of const);
  326. begin
  327. InternalLog(llDebug, aSender, aMessage, aParams);
  328. end;
  329. procedure TutlLogger.Log(const aSender: TObject; const aMessage: String; const aParams: array of const);
  330. begin
  331. InternalLog(llInfo, aSender, aMessage, aParams);
  332. end;
  333. procedure TutlLogger.Log(const aSender: String; const aMessage: String; const aParams: array of const);
  334. begin
  335. InternalLog(llInfo, aSender, aMessage, aParams);
  336. end;
  337. procedure TutlLogger.Warning(const aSender: TObject; const aMessage: String; const aParams: array of const);
  338. begin
  339. InternalLog(llWarning, aSender, aMessage, aParams);
  340. end;
  341. procedure TutlLogger.Warning(const aSender: String; const aMessage: String; const aParams: array of const);
  342. begin
  343. InternalLog(llWarning, aSender, aMessage, aParams);
  344. end;
  345. procedure TutlLogger.Error(const aSender: TObject; const aMessage: String; const aParams: array of const);
  346. begin
  347. InternalLog(llError, aSender, aMessage, aParams);
  348. end;
  349. procedure TutlLogger.Error(const aSender: String; const aMessage: String; const aParams: array of const);
  350. begin
  351. InternalLog(llError, aSender, aMessage, aParams);
  352. end;
  353. procedure TutlLogger.Error(const aSender: String; const aMessage: String; const aException: Exception);
  354. begin
  355. InternalLog(llError, aSender, utlCreateStackTrace(aMessage, aException), []);
  356. end;
  357. procedure TutlLogger.Error(const aSender: TObject; const aMessage: String; const aException: Exception);
  358. begin
  359. InternalLog(llError, aSender, utlCreateStackTrace(aMessage, aException), []);
  360. end;
  361. constructor TutlLogger.Create;
  362. var
  363. ll: TutlLogLevel;
  364. begin
  365. inherited Create;
  366. fConsumersLock:= TCriticalSection.Create;
  367. fConsumersLock.Acquire;
  368. try
  369. for ll:= low(ll) to high(ll) do begin
  370. fConsumers[ll]:= TutlLogConsumerList.Create;
  371. end;
  372. finally
  373. fConsumersLock.Release;
  374. end;
  375. end;
  376. destructor TutlLogger.Destroy;
  377. var
  378. ll: TutlLogLevel;
  379. begin
  380. fConsumersLock.Acquire;
  381. try
  382. for ll:= low(ll) to high(ll) do begin
  383. fConsumers[ll].Clear;
  384. FreeAndNil(fConsumers[ll]);
  385. end;
  386. finally
  387. fConsumersLock.Release;
  388. end;
  389. FreeAndNil(fConsumersLock);
  390. inherited Destroy;
  391. end;
  392. finalization
  393. FreeAndNil(utlLogger_Singleton);
  394. end.