You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

455 lines
14 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 TutlList<IutlLogConsumer>;
  36. { TutlLogger }
  37. TutlLogger = class(TObject)
  38. private
  39. fConsumersLock: TCriticalSection;
  40. fConsumers: array[TutlLogLevel] of TutlLogConsumerList;
  41. protected
  42. function FormatSender(const aSender: TObject): String;
  43. procedure InternalLog(const aLevel:TutlLogLevel; const aSender: TObject; const aMessage: String; const aParams: array of const); overload;
  44. procedure InternalLog(const aLevel:TutlLogLevel; const aSender: String; const aMessage: String; const aParams: array of const); overload;
  45. public
  46. procedure RegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter:TutlLogLevels=utlLogLevel_Any);
  47. procedure UnRegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter:TutlLogLevels=utlLogLevel_Any);
  48. class function FormatTime(const aTime:TDateTime): string;
  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. procedure SetAutoFree(aValue: Boolean);
  70. protected
  71. procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  72. public
  73. constructor Create(const aFilename: String; const aMode: TutlFileLoggerMode; const aAutoFree: Boolean = false);
  74. destructor Destroy; override;
  75. procedure Flush(); overload;
  76. published
  77. property AutoFlush: Boolean read fAutoFlush write fAutoFlush;
  78. end;
  79. { TutlConsoleLogger }
  80. TutlConsoleLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer)
  81. private
  82. fFreeConsoleCS: boolean;
  83. fConsoleCS: TCriticalSection;
  84. fOnBeforeLog: TNotifyEvent;
  85. fOnAfterLog: TNotifyEvent;
  86. protected
  87. procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); virtual;
  88. public
  89. property ConsoleCS: TCriticalSection read fConsoleCS;
  90. property OnBeforeLog: TNotifyEvent read fOnBeforeLog write fOnBeforeLog;
  91. property OnAfterLog: TNotifyEvent read fOnAfterLog write fOnAfterLog;
  92. constructor Create(const aSection: TCriticalSection = nil);
  93. destructor Destroy; override;
  94. end;
  95. { TutlEventLogger }
  96. TutlWriteLogEvent = procedure (const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String) of object;
  97. TutlEventLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer)
  98. private
  99. fWriteLogEvt: TutlWriteLogEvent;
  100. protected
  101. procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  102. public
  103. constructor Create(const aEvent: TutlWriteLogEvent);
  104. end;
  105. function utlLogger: TutlLogger;
  106. function utlCreateStackTrace(const aMessage: String; const aException: Exception): String;
  107. implementation
  108. var
  109. utlLogger_Singleton: TutlLogger;
  110. function utlLogger: TutlLogger;
  111. begin
  112. if not Assigned(utlLogger_Singleton) then
  113. utlLogger_Singleton:= TutlLogger.Create;
  114. Result:= utlLogger_Singleton;
  115. end;
  116. function utlCreateStackTrace(const aMessage: String; const aException: Exception): String;
  117. var
  118. i: Integer;
  119. frames: PPointer;
  120. begin
  121. result := aMessage;
  122. if Assigned(aException) then
  123. result := result + sLineBreak +
  124. ' Exception: ' + aException.ClassName + sLineBreak +
  125. ' Message: ' + aException.Message + sLineBreak +
  126. ' Thread: ' + IntToStr(GetCurrentThreadId) + sLineBreak +
  127. ' StackTrace:' + sLineBreak +
  128. ' ' + BackTraceStrFunc(ExceptAddr)
  129. else
  130. result := result + 'no Exception passed';
  131. frames := ExceptFrames;
  132. for i := 0 to ExceptFrameCount-1 do
  133. result := result + sLineBreak + ' ' + BackTraceStrFunc(frames[i]);
  134. end;
  135. { TutlFileLogger }
  136. function FileFlush(Handle: THandle): Boolean;
  137. begin
  138. {$IFDEF MSWINDOWS}
  139. Result:= FlushFileBuffers(Handle);
  140. {$ELSE}
  141. Result:= (fpfsync(Handle) = 0);
  142. {$ENDIF}
  143. end;
  144. procedure TutlFileLogger.SetAutoFree(aValue: Boolean);
  145. begin
  146. if fAutoFree = aValue then
  147. Exit;
  148. fAutoFree := aValue;
  149. if (fRefCount <= 0) and fAutoFree then
  150. Free;
  151. end;
  152. procedure TutlFileLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime;
  153. const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  154. var
  155. buf: AnsiString;
  156. begin
  157. if Assigned(fStream) then begin
  158. buf:= aLogger.FormatLine(aTime, aLevel, aSender, aMessage)+sLineBreak;
  159. fStream.Write(buf[1], Length(buf));
  160. if AutoFlush then
  161. FileFlush(fStream.Handle);
  162. end;
  163. end;
  164. constructor TutlFileLogger.Create(const aFilename: String; const aMode: TutlFileLoggerMode; const aAutoFree: Boolean = false);
  165. const
  166. RIGHTS: Cardinal = {$IFNDEF UNIX}fmShareDenyWrite{$ELSE}%0100100100 {-r--r--r--}{$ENDIF};
  167. begin
  168. try
  169. if (aMode = flmCreateNew) or not FileExists(aFilename) then begin
  170. if FileExists(aFilename) then
  171. DeleteFile(aFilename);
  172. fStream := TFileStream.Create(aFilename, fmCreate{$IFNDEF UNIX} or RIGHTS{$ENDIF}, RIGHTS)
  173. end else
  174. fStream := TFileStream.Create(aFilename, fmOpenReadWrite{$IFNDEF UNIX} or RIGHTS{$ENDIF}, RIGHTS);
  175. fStream.Position := fStream.Size;
  176. except
  177. on e: EStreamError do begin
  178. fStream:= nil;
  179. utlLogger.Error('Logger', 'Could not open log file "%s"',[aFilename]);
  180. end else
  181. raise;
  182. end;
  183. AutoFlush := true;
  184. fAutoFree := aAutoFree;
  185. end;
  186. destructor TutlFileLogger.Destroy;
  187. begin
  188. FreeAndNil(fStream);
  189. inherited Destroy;
  190. end;
  191. procedure TutlFileLogger.Flush;
  192. begin
  193. if Assigned(fStream) then
  194. FileFlush(fStream.Handle);
  195. end;
  196. { TutlConsoleLogger }
  197. procedure TutlConsoleLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime;
  198. const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  199. begin
  200. fConsoleCS.Acquire;
  201. try
  202. if Assigned(fOnBeforeLog) then
  203. fOnBeforeLog(Self);
  204. WriteLn(aLogger.FormatLine(aTime, aLevel, aSender, aMessage));
  205. if Assigned(fOnAfterLog) then
  206. fOnAfterLog(Self);
  207. finally
  208. fConsoleCS.Release;
  209. end;
  210. end;
  211. constructor TutlConsoleLogger.Create(const aSection: TCriticalSection);
  212. begin
  213. inherited Create;
  214. if Assigned(aSection) then
  215. fConsoleCS:= aSection
  216. else
  217. fConsoleCS:= TCriticalSection.Create;
  218. fFreeConsoleCS:= not Assigned(aSection);
  219. end;
  220. destructor TutlConsoleLogger.Destroy;
  221. begin
  222. if fFreeConsoleCS then
  223. FreeAndNil(fConsoleCS);
  224. inherited Destroy;
  225. end;
  226. { TutlEventLogger }
  227. procedure TutlEventLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime;
  228. const aLevel: TutlLogLevel; const aSender: string; const aMessage: String);
  229. begin
  230. fWriteLogEvt(aLogger,aTime, aLevel, aSender, aMessage);
  231. end;
  232. constructor TutlEventLogger.Create(const aEvent: TutlWriteLogEvent);
  233. begin
  234. inherited Create;
  235. fWriteLogEvt:= aEvent;
  236. end;
  237. { TutlLogger }
  238. procedure TutlLogger.RegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter: TutlLogLevels);
  239. var
  240. ll: TutlLogLevel;
  241. tmp: IutlLogConsumer;
  242. begin
  243. fConsumersLock.Acquire;
  244. try
  245. tmp := aConsumer; // HACK: store interface due to ref count bug :/
  246. for ll:= low(ll) to high(ll) do
  247. if (ll in aFilter) and (fConsumers[ll].IndexOf(tmp) < 0) then
  248. fConsumers[ll].Add(tmp);
  249. finally
  250. tmp := nil;
  251. fConsumersLock.Release;
  252. end;
  253. if llDebug in aFilter then
  254. aConsumer.WriteLog(Self, Now, llDebug, 'System', 'Attached to Logger');
  255. end;
  256. procedure TutlLogger.UnRegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter: TutlLogLevels);
  257. var
  258. ll: TutlLogLevel;
  259. begin
  260. fConsumersLock.Acquire;
  261. try
  262. for ll := low(ll) to high(ll) do
  263. if ll in aFilter then
  264. fConsumers[ll].Remove(aConsumer);
  265. finally
  266. fConsumersLock.Release;
  267. end;
  268. end;
  269. class function TutlLogger.FormatTime(const aTime: TDateTime): string;
  270. begin
  271. Result:= FormatDateTime('hh:nn:ss.zzz',aTime);
  272. end;
  273. function TutlLogger.FormatSender(const aSender: TObject): String;
  274. begin
  275. if Assigned(aSender) then
  276. result := format('%s[0x%P]', [aSender.ClassName, Pointer(aSender)])
  277. else
  278. result := '';
  279. end;
  280. class function TutlLogger.FormatLine(const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String): string;
  281. begin
  282. if (aSender <> '') then
  283. Result:= Format('%s %-9s %s: %s', [FormatTime(aTime), UpperCase(utlLogLevelStrings[aLevel]), aSender, aMessage])
  284. else
  285. Result:= Format('%s %-9s %s', [FormatTime(aTime), UpperCase(utlLogLevelStrings[aLevel]), aMessage]);
  286. end;
  287. procedure TutlLogger.InternalLog(const aLevel: TutlLogLevel; const aSender: TObject; const aMessage: String; const aParams: array of const);
  288. begin
  289. InternalLog(aLevel, FormatSender(aSender), aMessage, aParams);
  290. end;
  291. procedure TutlLogger.InternalLog(const aLevel: TutlLogLevel; const aSender: String; const aMessage: String; const aParams: array of const);
  292. var
  293. msg: string;
  294. when: TDateTime;
  295. i: integer;
  296. begin
  297. if length(aParams) = 0 then
  298. msg:= aMessage
  299. else
  300. msg:= Format(aMessage, aParams);
  301. when:= Now;
  302. fConsumersLock.Acquire;
  303. try
  304. for i:= 0 to fConsumers[aLevel].Count-1 do begin
  305. fConsumers[aLevel][i].WriteLog(Self, when, aLevel, aSender, msg);
  306. end;
  307. finally
  308. fConsumersLock.Release;
  309. end;
  310. end;
  311. procedure TutlLogger.Debug(const aSender: TObject; const aMessage: String; const aParams: array of const);
  312. begin
  313. InternalLog(llDebug, aSender, aMessage, aParams);
  314. end;
  315. procedure TutlLogger.Debug(const aSender: String; const aMessage: String; const aParams: array of const);
  316. begin
  317. InternalLog(llDebug, aSender, aMessage, aParams);
  318. end;
  319. procedure TutlLogger.Log(const aSender: TObject; const aMessage: String; const aParams: array of const);
  320. begin
  321. InternalLog(llInfo, aSender, aMessage, aParams);
  322. end;
  323. procedure TutlLogger.Log(const aSender: String; const aMessage: String; const aParams: array of const);
  324. begin
  325. InternalLog(llInfo, aSender, aMessage, aParams);
  326. end;
  327. procedure TutlLogger.Warning(const aSender: TObject; const aMessage: String; const aParams: array of const);
  328. begin
  329. InternalLog(llWarning, aSender, aMessage, aParams);
  330. end;
  331. procedure TutlLogger.Warning(const aSender: String; const aMessage: String; const aParams: array of const);
  332. begin
  333. InternalLog(llWarning, aSender, aMessage, aParams);
  334. end;
  335. procedure TutlLogger.Error(const aSender: TObject; const aMessage: String; const aParams: array of const);
  336. begin
  337. InternalLog(llError, aSender, aMessage, aParams);
  338. end;
  339. procedure TutlLogger.Error(const aSender: String; const aMessage: String; const aParams: array of const);
  340. begin
  341. InternalLog(llError, aSender, aMessage, aParams);
  342. end;
  343. procedure TutlLogger.Error(const aSender: String; const aMessage: String; const aException: Exception);
  344. begin
  345. InternalLog(llError, aSender, utlCreateStackTrace(aMessage, aException), []);
  346. end;
  347. procedure TutlLogger.Error(const aSender: TObject; const aMessage: String; const aException: Exception);
  348. begin
  349. InternalLog(llError, aSender, utlCreateStackTrace(aMessage, aException), []);
  350. end;
  351. constructor TutlLogger.Create;
  352. var
  353. ll: TutlLogLevel;
  354. begin
  355. inherited Create;
  356. fConsumersLock:= TCriticalSection.Create;
  357. fConsumersLock.Acquire;
  358. try
  359. for ll:= low(ll) to high(ll) do begin
  360. fConsumers[ll]:= TutlLogConsumerList.Create(true);
  361. end;
  362. finally
  363. fConsumersLock.Release;
  364. end;
  365. end;
  366. destructor TutlLogger.Destroy;
  367. var
  368. ll: TutlLogLevel;
  369. begin
  370. fConsumersLock.Acquire;
  371. try
  372. for ll:= low(ll) to high(ll) do begin
  373. fConsumers[ll].Clear;
  374. FreeAndNil(fConsumers[ll]);
  375. end;
  376. finally
  377. fConsumersLock.Release;
  378. end;
  379. FreeAndNil(fConsumersLock);
  380. inherited Destroy;
  381. end;
  382. finalization
  383. FreeAndNil(utlLogger_Singleton);
  384. end.