-
-
Notifications
You must be signed in to change notification settings - Fork 326
/
SynLog.pas
6167 lines (5822 loc) · 225 KB
/
SynLog.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// logging functions used by Synopse projects
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynLog;
(*
This file is part of Synopse framework.
Synopse framework. Copyright (c) Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (c)
the Initial Developer. All Rights Reserved.
Contributor(s):
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
*)
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
{$ifdef MSWINDOWS}
Windows,
Messages,
{$endif}
{$ifdef KYLIX3}
Types,
LibC,
SynKylix,
{$endif}
Classes,
{$ifndef LVCL}
SyncObjs, // for TEvent
Contnrs, // for TObjectList
{$ifdef HASINLINENOTX86}
Types,
{$endif}
{$endif}
{$ifndef NOVARIANTS}
Variants,
{$endif}
SysUtils,
SynLZ, // needed e.g. for TSynMapFile .mab format
SynCommons,
SynTable;
{ ************ Logging classes and functions }
type
/// a debugger symbol, as decoded by TSynMapFile from a .map file
TSynMapSymbol = packed record
/// symbol internal name
Name: RawUTF8;
/// starting offset of this symbol in the executable
// - addresses are integer, since map be <0 in Kylix .map files
Start: integer;
/// end offset of this symbol in the executable
// - addresses are integer, since map be <0 in Kylix .map files
Stop: integer;
end;
PSynMapSymbol = ^TSynMapSymbol;
/// a dynamic array of symbols, as decoded by TSynMapFile from a .map file
TSynMapSymbolDynArray = array of TSynMapSymbol;
/// a debugger unit, as decoded by TSynMapFile from a .map file
TSynMapUnit = packed record
/// Name, Start and Stop of this Unit
Symbol: TSynMapSymbol;
/// associated source file name
FileName: RawUTF8;
/// list of all mapped source code lines of this unit
Line: TIntegerDynArray;
/// start code address of each source code line
Addr: TIntegerDynArray;
end;
/// a dynamic array of units, as decoded by TSynMapFile from a .map file
TSynMapUnitDynArray = array of TSynMapUnit;
{$M+}
/// retrieve a .map file content, to be used e.g. with TSynLog to provide
// additional debugging information
// - original .map content can be saved as .mab file in a more optimized format
TSynMapFile = class
protected
fMapFile: TFileName;
fSymbol: TSynMapSymbolDynArray;
fUnit: TSynMapUnitDynArray;
fSymbols: TDynArray;
fUnits: TDynArrayHashed;
fSymCount, fUnitCount, fUnitSynLogIndex, fUnitSystemIndex: integer;
fCodeOffset: PtrUInt;
fHasDebugInfo: boolean;
public
/// get the available debugging information
// - if aExeName is specified, will use it in its search for .map/.mab
// - if aExeName is not specified, will use the currently running .exe/.dll
// - it will first search for a .map matching the file name: if found,
// will be read to retrieve all necessary debugging information - a .mab
// file will be also created in the same directory (if MabCreate is TRUE)
// - if .map is not not available, will search for the .mab file
// - if no .mab is available, will search for a .mab appended to the .exe/.dll
// - if nothing is available, will log as hexadecimal pointers, without
// debugging information
constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
/// save all debugging information in the .mab custom binary format
// - if no file name is specified, it will be saved as ExeName.mab or DllName.mab
// - this file content can be appended to the executable via SaveToExe method
// - this function returns the created file name
function SaveToFile(const aFileName: TFileName=''): TFileName;
/// save all debugging informat in our custom binary format
procedure SaveToStream(aStream: TStream);
/// append all debugging information to an executable (or library)
// - the executable name must be specified, because it's impossible to
// write to the executable of a running process
// - this method will work for .exe and for .dll (or .ocx)
procedure SaveToExe(const aExeName: TFileName);
/// save all debugging information as JSON content
// - may be useful from debugging purposes
procedure SaveToJson(W: TTextWriter); overload;
/// save all debugging information as a JSON file
// - may be useful from debugging purposes
procedure SaveToJson(const aJsonFile: TFileName; aHumanReadable: Boolean=false); overload;
/// add some debugging information about the supplied absolute memory address
// - will create a global TSynMapFile instance for the current process, if
// necessary
// - if no debugging information is available (.map or .mab), will write
// the raw address pointer as hexadecimal
// - under FPC, currently calls BacktraceStrFunc() which may be very slow
class procedure Log(W: TTextWriter; aAddressAbsolute: PtrUInt;
AllowNotCodeAddr: boolean);
/// compute the relative memory address from its absolute (pointer) value
function AbsoluteToOffset(aAddressAbsolute: PtrUInt): integer;
/// retrieve a symbol according to a relative code address
// - use fast O(log n) binary search
function FindSymbol(aAddressOffset: integer): integer;
/// retrieve an unit and source line, according to a relative code address
// - use fast O(log n) binary search
function FindUnit(aAddressOffset: integer; out LineNumber: integer): integer; overload;
/// retrieve an unit information, according to the unit name
// - will search within Units array
function FindUnit(const aUnitName: RawUTF8): integer; overload;
/// return the symbol location according to the supplied absolute address
// - i.e. unit name, symbol name and line number (if any), as plain text
// - returns '' if no match found
function FindLocation(aAddressAbsolute: PtrUInt): RawUTF8; overload;
/// return the symbol location according to the supplied ESynException
// - i.e. unit name, symbol name and line number (if any), as plain text
// - under FPC, currently calls BacktraceStrFunc() which may be very slow
class function FindLocation(exc: ESynException): RawUTF8; overload;
/// return the low-level stack trace exception information into human-friendly text
class function FindStackTrace(const Ctxt: TSynLogExceptionContext): TRawUTF8DynArray;
/// returns the file name of
// - if unitname = '', returns the main file name of the current executable
class function FindFileName(const unitname: RawUTF8): TFileName;
/// returns the global TSynMapFile instance associated with the current
// executable
class function FromCurrentExecutable: TSynMapFile;
/// all symbols associated to the executable
property Symbols: TSynMapSymbolDynArray read fSymbol;
/// all units, including line numbers, associated to the executable
property Units: TSynMapUnitDynArray read fUnit;
published
/// the associated file name
property FileName: TFileName read fMapFile;
/// equals true if a .map or .mab debugging information has been loaded
property HasDebugInfo: boolean read fHasDebugInfo;
end;
{$M-}
/// an exception which wouldn't be logged and intercepted by this unit
// - only this exact class will be recognized by TSynLog: inheriting it
// will trigger the interception, as any other regular exception
ESynLogSilent = class(ESynException);
{$M+} { we need the RTTI for the published methods of the logging classes }
TSynLog = class;
/// class-reference type (metaclass) of a TSynLog family
// - since TSynLog classes store their information per type, you usually
// will store a reference to a logging family (i.e. logging settings) using
// a TSynLogClass variable, whereas TSynLog would point to the active logging
// instance
TSynLogClass = class of TSynLog;
TSynLogFamily = class;
TSynLogFile = class;
{$M-}
/// a generic interface used for logging a method
// - you should create one TSynLog instance at the beginning of a block code
// using TSynLog.Enter: the ISynLog will be released automaticaly by the
// compiler at the end of the method block, marking it's executation end
// - all logging expect UTF-8 encoded text, i.e. usualy English text
ISynLog = interface(IUnknown)
['{527AC81F-BC41-4717-B089-3F74DE56F1AE}']
/// call this method to add some information to the log at a specified level
// - will use TTextWriter.Add(...,twOnSameLine) to append its content
// - % = #37 indicates a string, integer, floating-point, class parameter
// to be appended as text (e.g. class name), any variant as JSON...
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
// - if Instance is set, it will log the corresponding class name and address
// (to be used if you didn't call TSynLog.Enter() method first)
procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArgs: array of const;
Instance: TObject=nil); overload;
/// call this method to add some information to the log at a specified level
// - if Instance is set and Text is not '', it will log the corresponding
// class name and address (to be used e.g. if you didn't call TSynLog.Enter()
// method first)
// - if Instance is set and Text is '', will behave the same as
// Log(Level,Instance), i.e. write the Instance as JSON content
procedure Log(Level: TSynLogInfo; const Text: RawUTF8;
Instance: TObject=nil; TextTruncateAtLength: integer=maxInt); overload;
{$ifdef UNICODE}
/// call this method to add some VCL string to the log at a specified level
// - this overloaded version will avoid a call to StringToUTF8()
procedure Log(Level: TSynLogInfo; const Text: string; Instance: TObject=nil); overload;
{$endif}
/// call this method to add the content of an object to the log at a
// specified level
// - TSynLog will write the class and hexa address - TSQLLog will write the
// object JSON content
procedure Log(Level: TSynLogInfo; Instance: TObject); overload;
/// call this method to add the content of most low-level types to the log
// at a specified level
// - TSynLog will handle enumerations and dynamic array; TSQLLog will be
// able to write TObject/TSQLRecord and sets content as JSON
procedure Log(Level: TSynLogInfo; const aName: RawUTF8;
aTypeInfo: pointer; const aValue; Instance: TObject); overload;
/// call this method to add the caller address to the log at the specified level
// - if the debugging info is available from TSynMapFile, will log the
// unit name, associated symbol and source code line
procedure Log(Level: TSynLogInfo=sllTrace); overload;
/// call this method to add some multi-line information to the log at a
// specified level
// - LinesToLog content will be added, one line per one line, delimited
// by #13#10 (CRLF)
// - if a line starts with IgnoreWhenStartWith (already uppercase), it won't
// be added to the log content (to be used e.g. with '--' for SQL statements)
procedure LogLines(Level: TSynLogInfo; LinesToLog: PUTF8Char;
aInstance: TObject=nil; const IgnoreWhenStartWith: PAnsiChar=nil);
/// retrieve the associated logging instance
function Instance: TSynLog;
end;
{$ifndef DELPHI5OROLDER}
/// a mORMot-compatible calback definition
// - used to notify a remote mORMot server via interface-based serivces
// for any incoming event, using e.g. TSynLogCallbacks.Subscribe
ISynLogCallback = interface(IInvokable)
['{9BC218CD-A7CD-47EC-9893-97B7392C37CF}']
/// each line of the TTextWriter internal instance will trigger this method
// - the format is similar to TOnTextWriterEcho, as defined in SynCommons
// - an initial call with Level=sllNone and the whole previous Text may be
// transmitted, if ReceiveExistingKB is set for TSynLogCallbacks.Subscribe()
procedure Log(Level: TSynLogInfo; const Text: RawUTF8);
end;
/// store a subscribe to ISynLogCallback
TSynLogCallback = record
Levels: TSynLogInfos;
Callback: ISynLogCallback;
end;
/// store the all subscribed ISynLogCallback
TSynLogCallbackDynArray = array of TSynLogCallback;
/// can manage a list of ISynLogCallback registrations
TSynLogCallbacks = class(TSynPersistentLock)
protected
fCount: integer;
fCurrentlyEchoing: boolean;
public
/// direct access to the registration storage
Registration: TSynLogCallbackDynArray;
/// high-level access to the registration storage
Registrations: TDynArray;
/// the TSynLog family actually associated with those callbacks
TrackedLog: TSynLogFamily;
/// initialize the registration storage for a given TSynLogFamily instance
constructor Create(aTrackedLog: TSynLogFamily); reintroduce;
/// finalize the registration storage for a given TSynLogFamily instance
destructor Destroy; override;
/// register a callback for a given set of log levels
// - you can specify a number of KB of existing log content to send to the
// monitoring tool, before the actual real-time process
function Subscribe(const Levels: TSynLogInfos;
const Callback: ISynLogCallback; ReceiveExistingKB: cardinal=0): integer; virtual;
/// unregister a callback previously registered by Subscribe()
procedure Unsubscribe(const Callback: ISynLogCallback); virtual;
/// notify a given log event
// - matches the TOnTextWriterEcho signature
function OnEcho(Sender: TTextWriter; Level: TSynLogInfo;
const Text: RawUTF8): boolean;
published
/// how many registrations are currently defined
property Count: integer read fCount;
end;
{$endif}
/// this event can be set for a TSynLogFamily to archive any deprecated log
// into a custom compressed format
// - will be called by TSynLogFamily when TSynLogFamily.Destroy identify
// some outdated files
// - the aOldLogFileName will contain the .log file with full path
// - the aDestinationPath parameter will contain 'ArchivePath\log\YYYYMM\'
// - should return true on success, false on error
// - example of matching event handler are EventArchiveDelete/EventArchiveSynLZ
// or EventArchiveZip in SynZip.pas
// - this event handler will be called one time per .log file to archive,
// then one last time with aOldLogFileName='' in order to close any pending
// archive (used e.g. by EventArchiveZip to open the .zip only once)
TSynLogArchiveEvent = function(const aOldLogFileName, aDestinationPath: TFileName): boolean;
/// this event can be set for a TSynLogFamily to customize the file rotation
// - will be called by TSynLog.PerformRotation
// - should return TRUE if the function did process the file name
// - should return FALSE if the function did not do anything, so that the
// caller should perform the rotation as usual
TSynLogRotateEvent = function(aLog: TSynLog; const aOldLogFileName: TFileName): boolean;
/// how threading is handled by the TSynLogFamily
// - proper threading expects the TSynLog.NotifyThreadEnded method to be called
// when a thread is about to terminate, e.g. from TSQLRest.EndCurrentThread
// - by default, ptMergedInOneFile will indicate that all threads are logged
// in the same file, in occurence order
// - if set to ptOneFilePerThread, it will create one .log file per thread
// - if set to ptIdentifiedInOnFile, a new column will be added for each
// log row, with the corresponding ThreadID - LogView tool will be able to
// display per-thread logging, if needed - note that your application shall
// use a thread pool (just like all mORMot servers classes do), otherwise
// some random hash collision may occur if Thread IDs are not recycled enough
// - if set to ptNoThreadProcess, no thread information is gathered, and all
// Enter/Leave would be merged into a single call - but it may be mandatory
// to use this option if TSynLog.NotifyThreadEnded is not called (e.g. from
// legacy code), and that your process experiment instability issues
TSynLogPerThreadMode = (
ptMergedInOneFile, ptOneFilePerThread, ptIdentifiedInOnFile, ptNoThreadProcess);
/// how stack trace shall be computed during logging
TSynLogStackTraceUse = (stManualAndAPI,stOnlyAPI,stOnlyManual);
/// how file existing shall be handled during logging
TSynLogExistsAction = (acOverwrite, acAppend);
/// callback signature used by TSynLogFamilly.OnBeforeException
// - should return false to log the exception, or true to ignore it
TSynLogOnBeforeException = function(const aExceptionContext: TSynLogExceptionContext;
const aThreadName: RawUTF8): boolean of object;
/// store simple log-related settings
// - see also TDDDLogSettings in dddInfraSettings.pas and TSynDaemonSettings
// in mORMotService.pas, which may be more integrated
TSynLogSettings = class(TSynPersistent)
protected
fLevels: TSynLogInfos;
fDestinationPath: TFileName;
fRotateFileCount: integer;
fLogClass: TSynLogClass;
public
/// set some default values
constructor Create; override;
/// define the log information into the supplied TSynLog class
// - if you don't call this method, the logging won't be initiated
procedure SetLog(aLogClass: TSynLogClass = nil);
/// read-only access to the TSynLog class, if SetLog() has been called
property LogClass: TSynLogClass read fLogClass;
published
/// the log levels to be used for the log file
// - i.e. a combination of none or several logging event
// - if "*" is serialized, unneeded sllNone won't be part of the set
// - default is LOG_STACKTRACE
property Levels: TSynLogInfos read fLevels write fLevels;
/// allow to customize where the logs should be written
// - default is the system log folder (e.g. /var/log on Linux)
property DestinationPath: TFileName read fDestinationPath write fDestinationPath;
/// how many files will be rotated (default is 2)
property RotateFileCount: integer read fRotateFileCount write fRotateFileCount;
end;
/// regroup several logs under an unique family name
// - you should usualy use one family per application or per architectural
// module: e.g. a server application may want to log in separate files the
// low-level Communication, the DB access, and the high-level process
// - initialize the family settings before using them, like in this code:
// ! with TSynLogDB.Family do begin
// ! Level := LOG_VERBOSE;
// ! PerThreadLog := ptOneFilePerThread;
// ! DestinationPath := 'C:\Logs';
// ! end;
//- then use the logging system inside a method:
// ! procedure TMyDB.MyMethod;
// ! var log: ISynLog;
// ! begin
// ! log := TSynLogDB.Enter(self,'MyMethod');
// ! // do some stuff
// ! log.Log(sllInfo,'method run with no problem and value=%',[value]);
// ! end; // here log will be released and method leaving will be logged
TSynLogFamily = class
protected
fLevel, fLevelStackTrace: TSynLogInfos;
fArchiveAfterDays: Integer;
fArchivePath: TFileName;
fOnArchive: TSynLogArchiveEvent;
fOnRotate: TSynLogRotateEvent;
fPerThreadLog: TSynLogPerThreadMode;
fIncludeComputerNameInFileName: boolean;
fCustomFileName: TFileName;
fGlobalLog: TSynLog;
fSynLogClass: TSynLogClass;
fIdent: integer;
fDestinationPath: TFileName;
fDefaultExtension: TFileName;
fBufferSize: integer;
fHRTimestamp: boolean;
fLocalTimestamp: boolean;
fWithUnitName: boolean;
fWithInstancePointer: boolean;
fNoFile: boolean;
fAutoFlush: cardinal;
{$ifdef MSWINDOWS}
fNoEnvironmentVariable: boolean;
{$endif}
{$ifndef NOEXCEPTIONINTERCEPT}
fHandleExceptions: boolean;
{$endif}
fStackTraceLevel: byte;
fStackTraceUse: TSynLogStackTraceUse;
fFileExistsAction: TSynLogExistsAction;
fExceptionIgnore: TList;
fOnBeforeException: TSynLogOnBeforeException;
fEchoToConsole: TSynLogInfos;
fEchoToConsoleUseJournal: boolean;
fEchoCustom: TOnTextWriterEcho;
fEchoRemoteClient: TObject;
fEchoRemoteClientOwned: boolean;
fEchoRemoteEvent: TOnTextWriterEcho;
fEndOfLineCRLF: boolean;
fDestroying: boolean;
fRotateFileCurrent: cardinal;
fRotateFileCount: cardinal;
fRotateFileSize: cardinal;
fRotateFileAtHour: integer;
fRotateFileNoCompression: boolean;
function CreateSynLog: TSynLog;
procedure StartAutoFlush;
procedure SetDestinationPath(const value: TFileName);
procedure SetLevel(aLevel: TSynLogInfos);
procedure SynLogFileListEcho(const aEvent: TOnTextWriterEcho; aEventAdd: boolean);
procedure SetEchoToConsole(aEnabled: TSynLogInfos);
procedure SetEchoToConsoleUseJournal(aValue: boolean);
procedure SetEchoCustom(const aEvent: TOnTextWriterEcho);
function GetSynLogClassName: string;
function GetExceptionIgnoreCurrentThread: boolean;
procedure SetExceptionIgnoreCurrentThread(
aExceptionIgnoreCurrentThread: boolean);
public
/// intialize for a TSynLog class family
// - add it in the global SynLogFileFamily[] list
constructor Create(aSynLog: TSynLogClass);
/// release associated memory
// - will archive older DestinationPath\*.log files, according to
// ArchiveAfterDays value and ArchivePath
destructor Destroy; override;
/// retrieve the corresponding log file of this thread and family
// - creates the TSynLog if not already existing for this current thread
function SynLog: TSynLog;
/// register one object and one echo callback for remote logging
// - aClient is typically a mORMot's TSQLHttpClient or a TSynLogCallbacks
// instance as defined in this unit
// - if aClientOwnedByFamily is TRUE, its life time will be manage by this
// TSynLogFamily: it will stay alive until this TSynLogFamily is destroyed,
// or the EchoRemoteStop() method called
// - aClientEvent should be able to send the log row to the remote server
procedure EchoRemoteStart(aClient: TObject; const aClientEvent: TOnTextWriterEcho;
aClientOwnedByFamily: boolean);
/// stop echo remote logging
// - will free the aClient instance supplied to EchoRemoteStart
procedure EchoRemoteStop;
/// can be used to retrieve up to a specified amount of KB of existing log
// - expects a single file to be opened for this family
// - will retrieve the log content for the current file, truncating the
// text up to the specified number of KB (an up to 128 MB at most)
function GetExistingLog(MaximumKB: cardinal): RawUTF8;
/// callback to notify the current logger that its thread is finished
// - method follows TNotifyThreadEvent signature, which can be assigned to
// TSynBackgroundThreadAbstract.OnAfterExecute
// - is called e.g. by TSQLRest.EndCurrentThread
procedure OnThreadEnded(Sender: TThread);
/// you can add some exceptions to be ignored to this list
// - for instance, EConvertError may be added to the list, as such:
// ! TSQLLog.Family.ExceptionIgnore.Add(EConvertError);
// - you may also trigger ESynLogSilent exceptions for silent process
// - see also ExceptionIgnoreCurrentThread property, if you want a per-thread
// filtering of all exceptions
property ExceptionIgnore: TList read fExceptionIgnore;
/// allow to (temporarly) ignore exceptions in the current thread
// - this property will affect all TSynLogFamily instances, for the
// current thread
// - may be used in a try...finally block e.g. when notifying the exception
// to a third-party service, or during a particular process
// - see also ExceptionIgnore property - which is also checked in addition
// to this flag
property ExceptionIgnoreCurrentThread: boolean
read GetExceptionIgnoreCurrentThread write SetExceptionIgnoreCurrentThread;
/// you can let exceptions be ignored from a callback
// - if set and returns true, the given exception won't be logged
// - execution of this event handler is protected via the logs global lock
// - may be handy e.g. when working with code triggerring a lot of
// exceptions (e.g. Indy), where ExceptionIgnore could be refined
property OnBeforeException: TSynLogOnBeforeException
read fOnBeforeException write fOnBeforeException;
/// event called to archive the .log content after a defined delay
// - Destroy will parse DestinationPath folder for *.log files matching
// ArchiveAfterDays property value
// - you can set this property to EventArchiveDelete in order to delete deprecated
// files, or EventArchiveSynLZ to compress the .log file into our propertary
// SynLZ format: resulting file name will be ArchivePath\log\YYYYMM\*.log.synlz
// (use FileUnSynLZ function to uncompress it)
// - if you use SynZip.EventArchiveZip, the log files will be archived in
// ArchivePath\log\YYYYMM.zip
// - the aDestinationPath parameter will contain 'ArchivePath\log\YYYYMM\'
// - this event handler will be called one time per .log file to archive,
// then one last time with aOldLogFileName='' in order to close any pending
// archive (used e.g. by EventArchiveZip to open the .zip only once)
property OnArchive: TSynLogArchiveEvent read fOnArchive write fOnArchive;
/// event called to perform a custom file rotation
// - will be checked by TSynLog.PerformRotation to customize the rotation
// process and do not perform the default step, if the callback returns TRUE
property OnRotate: TSynLogRotateEvent read fOnRotate write fOnRotate;
/// if the some kind of events shall be echoed to the console
// - note that it will slow down the logging process a lot (console output
// is slow by nature under Windows, but may be convenient for interactive
// debugging of services, for instance
// - this property shall be set before any actual logging, otherwise it
// will have no effect
// - can be set e.g. to LOG_VERBOSE in order to echo every kind of events
// - EchoCustom or EchoToConsole can be activated separately
property EchoToConsole: TSynLogInfos read fEchoToConsole write SetEchoToConsole;
/// For Linux with journald
// - if true: redirect all EchoToConsole logging into journald service
// - such logs can be exported into a format whichcan be viewed by our
// LogView tool using a command (replacing UNIT with your unit name and
// PROCESS with the executable name):
// $ "journalctl -u UNIT --no-hostname -o short-iso-precise --since today | grep "PROCESS\[.*\]: . " > todaysLog.log"
property EchoToConsoleUseJournal: boolean read fEchoToConsoleUseJournal
write SetEchoToConsoleUseJournal;
/// can be set to a callback which will be called for each log line
// - could be used with a third-party logging system
// - EchoToConsole or EchoCustom can be activated separately
// - you may even disable the integrated file output, via NoFile := true
property EchoCustom: TOnTextWriterEcho read fEchoCustom write SetEchoCustom;
/// the associated TSynLog class
property SynLogClass: TSynLogClass read fSynLogClass;
published
/// the associated TSynLog class
property SynLogClassName: string read GetSynLogClassName;
/// index in global SynLogFileFamily[] and SynLogFileIndexThreadVar[] lists
property Ident: integer read fIdent;
/// the current level of logging information for this family
// - can be set e.g. to LOG_VERBOSE in order to log every kind of events
property Level: TSynLogInfos read fLevel write SetLevel;
/// the levels which will include a stack trace of the caller
// - by default, contains sllStackTrace,sllException,sllExceptionOS plus
// sllError,sllFail,sllLastError,sllDDDError for Delphi only - since FPC
// BacktraceStrFunc() function is very slow
// - exceptions will always trace the stack
property LevelStackTrace: TSynLogInfos read fLevelStackTrace write fLevelStackTrace;
/// the folder where the log must be stored
// - by default, is in the executable folder
property DestinationPath: TFileName read fDestinationPath write SetDestinationPath;
/// the file extension to be used
// - is '.log' by default
property DefaultExtension: TFileName read fDefaultExtension write fDefaultExtension;
/// if TRUE, the log file name will contain the Computer name - as '(MyComputer)'
property IncludeComputerNameInFileName: boolean read fIncludeComputerNameInFileName write fIncludeComputerNameInFileName;
/// can be used to customized the default file name
// - by default, the log file name is computed from the executable name
// (and the computer name if IncludeComputerNameInFileName is true)
// - you can specify your own file name here, to be used instead
// - this file name should not contain any folder, nor file extension (which
// are set by DestinationPath and DefaultExtension properties)
property CustomFileName: TFileName read fCustomFileName write fCustomFileName;
/// the folder where old log files must be compressed
// - by default, is in the executable folder, i.e. the same as DestinationPath
// - the 'log\' sub folder name will always be appended to this value
// - will then be used by OnArchive event handler to produce, with the
// current file date year and month, the final path (e.g.
// 'ArchivePath\Log\YYYYMM\*.log.synlz' or 'ArchivePath\Log\YYYYMM.zip')
property ArchivePath: TFileName read fArchivePath write fArchivePath;
/// number of days before OnArchive event will be called to compress
// or delete deprecated files
// - will be set by default to 7 days
// - will be used by Destroy to call OnArchive event handler on time
property ArchiveAfterDays: Integer read fArchiveAfterDays write fArchiveAfterDays;
/// the internal in-memory buffer size, in bytes
// - this is the number of bytes kept in memory before flushing to the hard
// drive; you can call TSynLog.Flush method or set AutoFlushTimeOut to true
// in order to force the writting to disk
// - is set to 4096 by default (4 KB is the standard hard drive cluster size)
property BufferSize: integer read fBufferSize write fBufferSize;
/// define how thread will be identified during logging process
// - by default, ptMergedInOneFile will indicate that all threads are logged
// in the same file, in occurence order (so multi-thread process on server
// side may be difficult to interpret)
// - if RotateFileCount and RotateFileSizeKB/RotateFileDailyAtHour are set,
// will be ignored (internal thread list shall be defined for one process)
property PerThreadLog: TSynLogPerThreadMode read fPerThreadLog write fPerThreadLog;
/// if TRUE, will log high-resolution time stamp instead of ISO 8601 date and time
// - this is less human readable, but allows performance profiling of your
// application on the customer side (using TSynLog.Enter methods)
// - set to FALSE by default, or if RotateFileCount and RotateFileSizeKB /
// RotateFileDailyAtHour are set (the high resolution frequency is set
// in the log file header, so expects a single file)
property HighResolutionTimestamp: boolean read fHRTimestamp write fHRTimestamp;
/// by default, time logging will use error-safe UTC values as reference
// - you may set this property to TRUE to store local time instead
property LocalTimestamp: boolean read fLocalTimestamp write fLocalTimestamp;
/// if TRUE, will log the unit name with an object instance if available
// - unit name is available from RTTI if the class has published properties
// - set to TRUE by default, for better debugging experience
property WithUnitName: boolean read fWithUnitName write fWithUnitName;
/// if TRUE, will log the pointer with an object instance class if available
// - set to TRUE by default, for better debugging experience
property WithInstancePointer: boolean read fWithInstancePointer write fWithInstancePointer;
/// the time (in seconds) after which the log content must be written on
// disk, whatever the current content size is
// - by default, the log file will be written for every 4 KB of log (see
// BufferSize property) - this will ensure that the main application won't
// be slow down by logging
// - in order not to loose any log, a background thread can be created
// and will be responsible of flushing all pending log content every
// period of time (e.g. every 10 seconds)
property AutoFlushTimeOut: cardinal read fAutoFlush write fAutoFlush;
{$ifdef MSWINDOWS}
/// force no environment variables to be written to the log file
// - may be usefull if they contain some sensitive information
property NoEnvironmentVariable: boolean read fNoEnvironmentVariable write fNoEnvironmentVariable;
{$endif}
/// force no log to be written to any file
// - may be usefull in conjunction e.g. with EchoToConsole or any other
// third-party logging component
property NoFile: boolean read fNoFile write fNoFile;
/// auto-rotation of logging files
// - set to 0 by default, meaning no rotation
// - can be set to a number of rotating files: rotation and compression will
// happen, and main file size will be up to RotateFileSizeKB number of bytes,
// or when RotateFileDailyAtHour time is reached
// - if set to 1, no .synlz backup will be created, so the main log file will
// be restarted from scratch when it reaches RotateFileSizeKB size or when
// RotateFileDailyAtHour time is reached
// - if set to a number > 1, some rotated files will be compressed using the
// SynLZ algorithm, and will be named e.g. as MainLogFileName.0.synlz ..
// MainLogFileName.7.synlz for RotateFileCount=9 (total count = 9, including
// 1 main log file and 8 .synlz files)
property RotateFileCount: cardinal read fRotateFileCount write fRotateFileCount;
/// maximum size of auto-rotated logging files, in kilo-bytes (per 1024 bytes)
// - specify the maximum file size upon which .synlz rotation takes place
// - is not used if RotateFileCount is left to its default 0
property RotateFileSizeKB: cardinal read fRotateFileSize write fRotateFileSize;
/// fixed hour of the day where logging files rotation should be performed
// - by default, equals -1, meaning no rotation
// - you can set a time value between 0 and 23 to force the rotation at this
// specified hour
// - is not used if RotateFileCount is left to its default 0
property RotateFileDailyAtHour: integer read fRotateFileAtHour write fRotateFileAtHour;
/// if set to TRUE, no #.synlz will be created at rotation but plain #.log file
property RotateFileNoCompression: boolean read fRotateFileNoCompression write fRotateFileNoCompression;
/// the recursive depth of stack trace symbol to write
// - used only if exceptions are handled, or by sllStackTrace level
// - default value is 30, maximum is 255
// - if stOnlyAPI is defined as StackTraceUse under Windows XP, maximum
// value may be around 60, due to RtlCaptureStackBackTrace() API limitations
property StackTraceLevel: byte read fStackTraceLevel write fStackTraceLevel;
/// how the stack trace shall use only the Windows API
// - the class will use low-level RtlCaptureStackBackTrace() API to retrieve
// the call stack: in some cases, it is not able to retrieve it, therefore
// a manual walk of the stack can be processed - since this manual call can
// trigger some unexpected access violations or return wrong positions,
// you can disable this optional manual walk by setting it to stOnlyAPI
// - default is stManualAndAPI, i.e. use RtlCaptureStackBackTrace() API and
// perform a manual stack walk if the API returned no address (or <3); but
// within the IDE, it will use stOnlyAPI, to ensure no annoyning AV occurs
property StackTraceUse: TSynLogStackTraceUse read fStackTraceUse write fStackTraceUse;
/// how existing log file shall be handled
property FileExistsAction: TSynLogExistsAction read fFileExistsAction write fFileExistsAction;
/// define how the logger will emit its line feed
// - by default (FALSE), a single LF (#10) char will be written, to save
// storage space
// - you can set this property to TRUE, so that CR+LF (#13#10) chars will
// be appended instead
// - TSynLogFile class and our LogView tool will handle both patterns
property EndOfLineCRLF: boolean read fEndOfLineCRLF write fEndOfLineCRLF;
end;
/// TSynLogThreadContext will define a dynamic array of such information
// - used by TSynLog.Enter methods to handle recursivity calls tracing
TSynLogThreadRecursion = record
/// associated class instance to be displayed
Instance: TObject;
/// method name (or message) to be displayed
// - may be a RawUTF8 if MethodNameLocal=mnEnterOwnMethodName
MethodName: PUTF8Char;
/// internal reference count used at this recursion level by TSynLog._AddRef
RefCount: integer;
/// the caller address, ready to display stack trace dump if needed
Caller: PtrUInt;
/// the time stamp at enter time
EnterTimestamp: Int64;
/// if the method name is local, i.e. shall not be displayed at Leave()
MethodNameLocal: (mnAlways, mnEnter, mnLeave, mnEnterOwnMethodName);
end;
PSynLogThreadRecursion = ^TSynLogThreadRecursion;
/// thread-specific internal context used during logging
// - this structure is a hashed-per-thread variable
TSynLogThreadContext = record
/// the corresponding Thread ID
ID: TThreadID;
/// number of items stored in Recursion[]
RecursionCount: integer;
/// number of items available in Recursion[]
// - faster than length(Recursion)
RecursionCapacity: integer;
/// used by TSynLog.Enter methods to handle recursivity calls tracing
Recursion: array of TSynLogThreadRecursion;
/// the associated thread name
ThreadName: RawUTF8;
end;
// pointer to thread-specific context information
PSynLogThreadContext = ^TSynLogThreadContext;
/// file stream which ignores I/O write errors
// - in case disk space is exhausted, TFileStreamWithoutWriteError.WriteBuffer
// won't throw any exception, so application will continue to work
// - used by TSynLog to let the application continue with no exception,
// even in case of a disk/partition full of logs
TFileStreamWithoutWriteError = class(TFileStream)
public
/// this overriden function returns Count, as if it was always sucessfull
function Write(const Buffer; Count: Longint): Longint; override;
end;
/// a per-family and/or per-thread log file content
// - you should create a sub class per kind of log file
// ! TSynLogDB = class(TSynLog);
// - the TSynLog instance won't be allocated in heap, but will share a
// per-thread (if Family.PerThreadLog=ptOneFilePerThread) or global private
// log file instance
// - was very optimized for speed, if no logging is written, and even during
// log write (using an internal TTextWriter)
// - can use available debugging information via the TSynMapFile class, for
// stack trace logging for exceptions, sllStackTrace, and Enter/Leave labelling
TSynLog = class(TObject, ISynLog)
protected
fFamily: TSynLogFamily;
fWriter: TTextWriterWithEcho;
fWriterClass: TTextWriterClass;
fWriterStream: TStream;
fThreadContext: PSynLogThreadContext;
fThreadID: TThreadID;
fThreadLastHash: integer;
fThreadIndex: integer;
fStartTimestamp: Int64;
fCurrentTimestamp: Int64;
{$ifndef LINUX}
fFrequencyTimestamp: Int64;
{$endif}
fStartTimestampDateTime: TDateTime;
fStreamPositionAfterHeader: cardinal;
fFileName: TFileName;
fFileRotationSize: cardinal;
fFileRotationNextHour: Int64;
fThreadHash: TWordDynArray; // 8 KB buffer
fThreadIndexReleased: TWordDynArray;
fThreadIndexReleasedCount: integer;
fThreadContexts: array of TSynLogThreadContext;
fThreadContextCount: integer;
fCurrentLevel: TSynLogInfo;
fInternalFlags: set of (logHeaderWritten, logInitDone);
fDisableRemoteLog: boolean;
{$ifndef NOEXCEPTIONINTERCEPT} // for IsBadCodePtr() or any internal exception
fThreadHandleExceptionBackup: TSynLog;
{$endif}
{$ifdef FPC}
function QueryInterface(
{$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
function _AddRef: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
function _Release: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
{$else}
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{$endif}
class function FamilyCreate: TSynLogFamily;
procedure CreateLogWriter; virtual;
procedure LogInternal(Level: TSynLogInfo; const TextFmt: RawUTF8;
const TextArgs: array of const; Instance: TObject); overload;
procedure LogInternal(Level: TSynLogInfo; const Text: RawUTF8;
Instance: TObject; TextTruncateAtLength: integer); overload;
procedure LogInternal(Level: TSynLogInfo; const aName: RawUTF8;
aTypeInfo: pointer; const aValue; Instance: TObject); overload;
// any call to this method MUST call LogTrailerUnLock
function LogHeaderLock(Level: TSynLogInfo; AlreadyLocked: boolean): boolean;
procedure LogTrailerUnLock(Level: TSynLogInfo); {$ifdef HASINLINENOTX86}inline;{$endif}
procedure LogCurrentTime; virtual;
procedure LogFileInit; virtual;
procedure LogFileHeader; virtual;
{$ifndef DELPHI5OROLDER}
procedure AddMemoryStats; virtual;
{$endif}
procedure AddErrorMessage(Error: cardinal);
procedure AddStackTrace(Stack: PPtrUInt);
procedure ComputeFileName; virtual;
function GetFileSize: Int64; virtual;
procedure PerformRotation; virtual;
procedure AddRecursion(aIndex: integer; aLevel: TSynLogInfo);
procedure LockAndGetThreadContext; {$ifdef HASINLINENOTX86}inline;{$endif}
procedure GetThreadContextInternal;
function NewRecursion: PSynLogThreadRecursion;
procedure ThreadContextRehash;
function Instance: TSynLog;
function ConsoleEcho(Sender: TTextWriter; Level: TSynLogInfo;
const Text: RawUTF8): boolean; virtual;
public
/// intialize for a TSynLog class instance
// - WARNING: not to be called directly! Use Enter or Add class function instead
constructor Create(aFamily: TSynLogFamily=nil); virtual;
/// release all memory and internal handles
destructor Destroy; override;
/// flush all log content to file
// - if ForceDiskWrite is TRUE, will wait until written on disk (slow)
procedure Flush(ForceDiskWrite: boolean);
/// flush all log content to file and close the file
procedure CloseLogFile;
/// flush all log content to file, close the file, and release the instance
// - you should never call the Free method directly, since the instance
// is registered in a global TObjectList and an access violation may
// occur at application closing: you can use this Release method if you
// are sure that you won't need this TSynLog instance any more
// - ensure there is no pending Leave element in a stack-allocated ISynLog
// (see below)
// - can be used e.g. to release the instance when finishing a thread when
// Family.PerThreadLog=ptOneFilePerThread:
// ! var
// ! TThreadLogger : TSynLogClass = TSynLog;
// !
// ! procedure TMyThread.Execute;
// ! var log : ISynLog;
// ! begin
// ! log := TThreadLogger.Enter(self);
// ! ...
// ! log := nil; // to force logging end of method
// ! TThreadLogger.SynLog.Release;
// ! end;
procedure Release;
/// you may call this method when a thread is ended
// - should be called in the thread context which is about to terminate,
// in a situation where no other logging may occur from this thread any more
// - it will release all thread-specific resource used by this TSynLog
// - is called e.g. by TSQLRest.EndCurrentThread, via TSynLogFamily.OnThreadEnded
procedure NotifyThreadEnded;
/// handle generic method enter / auto-leave tracing
// - this is the main method to be called within a procedure/function to trace:
// ! procedure TMyDB.SQLExecute(const SQL: RawUTF8);
// ! var log: ISynLog;
// ! begin
// ! log := TSynLogDB.Enter(self,'SQLExecute');
// ! // do some stuff
// ! log.Log(sllInfo,'SQL=%',[SQL]);
// ! end; // here log will be released, and method leaving will be logged
// - returning a ISynLog interface will allow you to have an automated
// sllLeave log created when the method is left (thanks to the hidden
// try..finally block generated by the compiler to protect the ISynLog var)
// - WARNING: due to a limitation (feature?) of the FPC compiler and
// Delphi 10.4 and later, you NEED to hold the returned value into a
// local ISynLog variable; as a benefit, it is always convenient to define
// a local variable to store the returned ISynLog and use it for any
// specific logging within the method execution
// - on Delphi earlier to 10.4 (and not FPC), you could just call Enter()
// inside the method block, without any ISynLog interface variable - but
// it is not very future-proof to write the following code:
// ! procedure TMyDB.SQLFlush;
// ! begin
// ! TSynLogDB.Enter(self,'SQLFlush');
// ! // do some stuff
// ! end;
// - if no Method name is supplied, it will use the caller address, and
// will write it as hexa and with full unit and symbol name, if the debugging
// information is available (i.e. if TSynMapFile retrieved the .map content;
// note that this is not available yet on FPC):
// ! procedure TMyDB.SQLFlush;
// ! var log: ISynLog;
// ! begin
// ! log := TSynLogDB.Enter(self);
// ! // do some stuff
// ! end;
// - note that supplying a method name is faster than using the .map content:
// if you want accurate profiling, it's better to use a method name or not to
// use a .map file - note that this method name shall be a constant, and not
// a locally computed variable, since it may trigger some random GPF at
// runtime - if it is a local variable, you can set aMethodNameLocal=true
// - if TSynLogFamily.HighResolutionTimestamp is TRUE, high-resolution
// time stamp will be written instead of ISO 8601 date and time: this will
// allow performance profiling of the application on the customer side
// - Enter() will write the class name (and the unit name for classes with
// published properties, if TSynLogFamily.WithUnitName is true) for both
// enter (+) and leave (-) events:
// $ 20110325 19325801 + MyDBUnit.TMyDB(004E11F4).SQLExecute
// $ 20110325 19325801 info SQL=SELECT * FROM Table;
// $ 20110325 19325801 - 01.512.320
class function Enter(aInstance: TObject=nil; aMethodName: PUTF8Char=nil;
aMethodNameLocal: boolean=false): ISynLog; overload;
/// handle method enter / auto-leave tracing, with some custom text
// - this overloaded method would not write the method name, but the supplied
// text content, after expanding the parameters like FormatUTF8()
// - it will append the corresponding sllLeave log entry when the method ends
class function Enter(const TextFmt: RawUTF8; const TextArgs: array of const;
aInstance: TObject=nil): ISynLog; overload;
/// retrieve the current instance of this TSynLog class
// - to be used for direct logging, without any Enter/Leave:
// ! TSynLogDB.Add.Log(llError,'The % statement didn''t work',[SQL]);
// - to be used for direct logging, without any Enter/Leave (one parameter
// version - just the same as previous):
// ! TSynLogDB.Add.Log(llError,'The % statement didn''t work',SQL);
// - is just a wrapper around Family.SynLog - the same code will work:
// ! TSynLogDB.Family.SynLog.Log(llError,'The % statement didn''t work',[SQL]);
class function Add: TSynLog; {$ifdef HASINLINENOTX86}inline;{$endif}
/// retrieve the family of this TSynLog class type
class function Family: TSynLogFamily; overload; {$ifdef HASINLINENOTX86}inline;{$endif}
/// returns a logging class which will never log anything
// - i.e. a TSynLog sub-class with Family.Level := []
class function Void: TSynLogClass;
/// low-level method helper which can be called to make debugging easier
// - log some warning message to the TSynLog family
// - will force a manual breakpoint if tests are run from the IDE
class procedure DebuggerNotify(Level: TSynLogInfo;
const Format: RawUTF8; const Args: array of const);
/// call this method to add some information to the log at the specified level
// - will use TTextWriter.Add(...,twOnSameLine) to append its content
// - % = #37 indicates a string, integer, floating-point, class parameter
// to be appended as text (e.g. class name), any variant as JSON...
// - note that cardinal values should be type-casted to Int64() (otherwise
// the integer mapped value will be transmitted, therefore wrongly)
procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArgs: array of const;
aInstance: TObject=nil); overload;
/// same as Log(Level,TextFmt,[]) but with one RawUTF8 parameter
procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArg: RawUTF8;
aInstance: TObject=nil); overload;
/// same as Log(Level,TextFmt,[]) but with one Int64 parameter
procedure Log(Level: TSynLogInfo; const TextFmt: RawUTF8; const TextArg: Int64;
aInstance: TObject=nil); overload;
/// call this method to add some information to the log at the specified level
// - if Instance is set and Text is not '', it will log the corresponding
// class name and address (to be used e.g. if you didn't call TSynLog.Enter()
// method first) - for instance
// ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector);
// will append this line to the log:
// $ 0000000000002DB9 debug TObjectList(00425E68) GarbageCollector
// - if Instance is set and Text is '', will behave the same as
// Log(Level,Instance), i.e. write the Instance as JSON content
procedure Log(Level: TSynLogInfo; const Text: RawUTF8;
aInstance: TObject=nil; TextTruncateAtLength: integer=maxInt); overload;