-
-
Notifications
You must be signed in to change notification settings - Fork 120
/
mormot.core.log.pas
7736 lines (7316 loc) · 252 KB
/
mormot.core.log.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
/// Framework Core Logging
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.log;
{
*****************************************************************************
Logging functions shared by all framework units
- Debug Symbols Processing from Delphi .map or FPC/GDB DWARF
- Logging via TSynLogFamily, TSynLog, ISynLog
- High-Level Logs and Exception Related Features
- Efficient .log File Access via TSynLogFile
- SysLog Messages Support as defined by RFC 5424
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.buffers,
mormot.core.data,
mormot.core.rtti,
mormot.core.json,
mormot.core.unicode,
mormot.core.text,
mormot.core.datetime;
{ ************** Debug Symbols Processing from Delphi .map or FPC/GDB DWARF }
type
/// a debugger symbol, as decoded by TDebugFile from a .map/.dbg file
TDebugSymbol = packed record
/// symbol internal name
Name: RawUtf8;
/// starting offset of this symbol in the executable
Start: integer;
/// end offset of this symbol in the executable
Stop: integer;
end;
PDebugSymbol = ^TDebugSymbol;
/// a dynamic array of symbols, as decoded by TDebugFile from a .map/.dbg file
TDebugSymbolDynArray = array of TDebugSymbol;
/// a debugger unit, as decoded by TDebugFile from a .map/.dbg file
TDebugUnit = packed record
/// Name, Start and Stop of this Unit
Symbol: TDebugSymbol;
/// 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;
PDebugUnit = ^TDebugUnit;
/// a dynamic array of units, as decoded by TDebugFile from a .map/.dbg file
TDebugUnitDynArray = array of TDebugUnit;
/// process a .map/.dbg file content, to be used e.g. with TSynLog to provide
// additional debugging information for a given executable
// - debug info can be saved as .mab file in a much more optimized format
// (e.g. mormot2tests 4MB .map into a 280KB .mab, 13MB .dbg into a 290KB .mab)
// - on FPC, DWARF symbols embedded to the executable can also be retrieved - but
// you would better use an external .dbg file then convert it into a .mab
// - on FPC, you don't need to specifly the -gl compiler switch
// - location of a source code information from its address is below 10us
TDebugFile = class(TSynPersistent)
protected
fDebugFile: TFileName;
fSymbol: TDebugSymbolDynArray;
fUnit: TDebugUnitDynArray;
fSymbols, fUnits: TDynArray;
fSymbolsCount, fUnitsCount: integer;
fCodeOffset: PtrUInt;
fHasDebugInfo: boolean;
// called by Create() constructor
procedure GenerateFromMapOrDbg(aDebugToConsole: boolean);
function LoadMab(const aMabFile: TFileName): boolean;
public
/// get the available debugging information
// - if aExeName is specified, will use it in its search for .map/.dbg/.mab
// - if aExeName is not specified, will use the currently running .exe/.dll
// - it will first search for a .map/.dbg 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/.dbg 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;
DebugToConsole: boolean = false); reintroduce;
/// 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;
aJsonFormat: TTextWriterJsonFormat = jsonCompact); overload;
/// add some debugging information about the supplied absolute memory address
// - create a global TDebugFile instance for the current process, if needed
// - if no debugging information is available (.map/.dbg/.mab), will write
// the raw address pointer as hexadecimal
class function Log(W: TTextWriter; aAddressAbsolute: PtrUInt;
AllowNotCodeAddr: boolean; SymbolNameNotFilename: boolean = false): boolean;
/// compute the relative memory address from its absolute (pointer) value
function AbsoluteToOffset(aAddressAbsolute: PtrUInt): integer;
{$ifdef HASINLINE}inline;{$endif}
/// check if this memory address is part of the code segments
function IsCode(aAddressAbsolute: PtrUInt): boolean;
/// retrieve a symbol according to a relative code address
// - use fast O(log n) binary search
function FindSymbol(aAddressOffset: integer): PtrInt;
/// 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): PtrInt; overload;
/// retrieve an unit, according to a relative code address
// - use fast O(log n) binary search
function FindUnit(aAddressOffset: integer): PtrInt; overload;
/// retrieve an unit information, according to the unit name
// - will search within Units array
function FindUnit(const aUnitName: RawUtf8): PtrInt; overload;
/// return the symbol location according to the supplied absolute address
// - filename, symbol name and line number (if any), as plain text, e.g.
// '4cb765 ../src/core/mormot.core.base.pas statuscodeissuccess (11183)' on FPC
// - returns only the hexadecimal value if no match is found in .map/.gdb info
function FindLocation(aAddressAbsolute: PtrUInt): RawUtf8; overload;
/// return the symbol location according to the supplied absolute address
// - filename, symbol name and line number (if any), as plain text, e.g.
// '4cb765 ../src/core/mormot.core.base.pas statuscodeissuccess (11183)' on FPC
// - returns only the hexadecimal value if no match is found in .map/.gdb info
// - won't allocate any heap memory during the text creation
// - mormot.core.os.pas' GetExecutableLocation() redirects to this method
function FindLocationShort(aAddressAbsolute: PtrUInt): ShortString;
/// load .map/.gdb info and return the symbol location according
// to the supplied ESynException
// - i.e. unit name, symbol name and line number (if any), as plain text
class function FindLocation(exc: ESynException): RawUtf8; overload;
/// load .map/.gdb info and returns the file name of a given unit
// - if unitname = '', returns the main file name of the current executable
class function FindFileName(const unitname: RawUtf8): TFileName;
{$ifdef FPC}
/// load DWARF .gdb info and replace FPC RTL BacktraceStrFunc()
// - uses much less disk space (e.g. 13MB .gdb into 284KB)
// - is much faster: around 10us per call, whereas lnfodwrf is 20ms
class function RegisterBacktraceStrFunc: boolean;
{$endif FPC}
/// all symbols, mainly function and method names and addresses
property Symbols: TDebugSymbolDynArray
read fSymbol;
/// all units, including line numbers, associated to the executable
property Units: TDebugUnitDynArray
read fUnit;
published
/// the associated file name
// - e.g. 'exec.map', 'exec.dbg' or even plain 'exec'/'exec.exe'
property FileName: TFileName
read fDebugFile;
/// equals true if a .map/.dbg or .mab debugging information has been loaded
property HasDebugInfo: boolean
read fHasDebugInfo;
end;
{$ifndef PUREMORMOT2}
// backward compatibility type redirection
TSynMapFile = TDebugFile;
{$endif PUREMORMOT2}
{ ************** Logging via TSynLogFamily, TSynLog, ISynLog }
type
/// a list of lof events families, used to gather events by type
TSynLogFilter = (
lfNone,
lfAll,
lfErrors,
lfExceptions,
lfProfile,
lfDatabase,
lfClientServer,
lfDebug,
lfCustom,
lfDDD);
const
/// up to 16 TSynLogFamily, i.e. TSynLog children classes can be defined
MAX_SYNLOGFAMILY = 15;
/// can be set to TSynLogFamily.Level in order to log all available events
LOG_VERBOSE: TSynLogInfos =
[succ(sllNone)..high(TSynLogInfo)];
/// contains the logging levels for which stack trace should be dumped
// - which are mainly exceptions or application errors
LOG_STACKTRACE: TSynLogInfos =
[sllException,
sllExceptionOS,
sllLastError,
sllError,
sllDDDError];
/// the text equivalency of each logging level, as written in the log file
// - PCardinal(@LOG_LEVEL_TEXT[L][3])^ will be used for fast level matching
// so text must be unique for characters [3..6] -> e.g. 'UST4'
LOG_LEVEL_TEXT: array[TSynLogInfo] of string[7] = (
' ', // sllNone
' info ', // sllInfo
' debug ', // sllDebug
' trace ', // sllTrace
' warn ', // sllWarning
' ERROR ', // sllError
' + ', // sllEnter
' - ', // sllLeave
' OSERR ', // sllLastError
' EXC ', // sllException
' EXCOS ', // sllExceptionOS
' mem ', // sllMemory
' stack ', // sllStackTrace
' fail ', // sllFail
' SQL ', // sllSQL
' cache ', // sllCache
' res ', // sllResult
' DB ', // sllDB
' http ', // sllHTTP
' clnt ', // sllClient
' srvr ', // sllServer
' call ', // sllServiceCall
' ret ', // sllServiceReturn
' auth ', // sllUserAuth
' cust1 ', // sllCustom1
' cust2 ', // sllCustom2
' cust3 ', // sllCustom3
' cust4 ', // sllCustom4
' rotat ', // sllNewRun
' dddER ', // sllDDDError
' dddIN ', // sllDDDInfo
' mon '); // sllMonitoring
/// RGB colors corresponding to each logging level
// - matches the TColor values, as used by the VCL
// - first array is for the background, second is for the text (black/white)
LOG_LEVEL_COLORS: array[boolean, TSynLogInfo] of integer = (
($FFFFFF, // sllNone
$DCC0C0, // sllInfo
$DCDCDC, // sllDebug
$C0C0C0, // sllTrace
$8080C0, // sllWarning
$8080FF, // sllError
$C0DCC0, // sllEnter
$DCDCC0, // sllLeave
$C0C0F0, // sllLastError
$C080FF, // sllException
$C080F0, // sllExceptionOS
$C080C0, // sllMemory
$C080C0, // sllStackTrace
$4040FF, // sllFail
$B08080, // sllSQL
$B0B080, // sllCache
$8080DC, // sllResult
$80DC80, // sllDB
$DC8080, // sllHTTP
$DCFF00, // sllClient
$DCD000, // sllServer
$DCDC80, // sllServiceCall
$DC80DC, // sllServiceReturn
$DCDCDC, // sllUserAuth
$D0D0D0, // sllCustom1
$D0D0DC, // sllCustom2
$D0D0C0, // sllCustom3
$D0D0E0, // sllCustom4
$20E0D0, // sllNewRun
$8080FF, // sllDDDError
$DCCDCD, // sllDDDInfo
$C0C0C0), // sllMonitoring
// black/white text corresponding to each colored background:
($000000, // sllNone
$000000, // sllInfo
$000000, // sllDebug
$000000, // sllTrace
$000000, // sllWarning
$FFFFFF, // sllError
$000000, // sllEnter
$000000, // sllLeave
$FFFFFF, // sllLastError
$FFFFFF, // sllException
$FFFFFF, // sllExceptionOS
$000000, // sllMemory
$000000, // sllStackTrace
$FFFFFF, // sllFail
$FFFFFF, // sllSQL
$000000, // sllCache
$FFFFFF, // sllResult
$000000, // sllDB
$000000, // sllHTTP
$000000, // sllClient
$000000, // sllServer
$000000, // sllServiceCall
$000000, // sllServiceReturn
$000000, // sllUserAuth
$000000, // sllCustom1
$000000, // sllCustom2
$000000, // sllCustom3
$000000, // sllCustom4
$000000, // sllNewRun
$FFFFFF, // sllDDDError
$000000, // sllDDDInfo
$000000));// sllMonitoring
/// console colors corresponding to each logging level
// - to be used with mormot.core.os TextColor()
LOG_CONSOLE_COLORS: array[TSynLogInfo] of TConsoleColor = (
ccLightGray, // sllNone
ccWhite, // sllInfo
ccLightGray, // sllDebug
ccLightBlue, // sllTrace
ccBrown, // sllWarning
ccLightRed, // sllError
ccGreen, // sllEnter
ccGreen, // sllLeave
ccLightRed, // sllLastError
ccLightRed, // sllException
ccLightRed, // sllExceptionOS
ccLightGray, // sllMemory
ccCyan, // sllStackTrace
ccLightRed, // sllFail
ccBrown, // sllSQL
ccBlue, // sllCache
ccLightCyan, // sllResult
ccMagenta, // sllDB
ccCyan, // sllHTTP
ccLightCyan, // sllClient
ccLightCyan, // sllServer
ccLightMagenta, // sllServiceCall
ccLightMagenta, // sllServiceReturn
ccMagenta, // sllUserAuth
ccLightGray, // sllCustom1
ccLightGray, // sllCustom2
ccLightGray, // sllCustom3
ccLightGray, // sllCustom4
ccLightMagenta, // sllNewRun
ccLightRed, // sllDDDError
ccWhite, // sllDDDInfo
ccLightBlue); // sllMonitoring
/// how TLogFilter map TSynLogInfo events
LOG_FILTER: array[TSynLogFilter] of TSynLogInfos = (
[], // lfNone
[succ(sllNone)..high(TSynLogInfo)], // lfAll
[sllError, sllLastError, sllException, sllExceptionOS], // lfErrors
[sllException, sllExceptionOS], // lfExceptions
[sllEnter, sllLeave], // lfProfile
[sllSQL, sllCache, sllDB], // lfDatabase
[sllClient, sllServer, sllServiceCall, sllServiceReturn], // lfClientServer
[sllDebug, sllTrace, sllEnter], // lfDebug
[sllCustom1..sllCustom4], // lfCustom
[sllDDDError, sllDDDInfo]); // lfDDD
/// may be used to log as Debug or Error event, depending on an Error: boolean
LOG_DEBUGERROR: array[boolean] of TSynLogInfo = (
sllDebug,
sllError);
/// may be used to log as Trace or Warning event, depending on an Error: boolean
LOG_TRACEWARNING: array[boolean] of TSynLogInfo = (
sllTrace,
sllWarning);
/// may be used to log as Trace or Error event, depending on an Error: boolean
LOG_TRACEERROR: array[boolean] of TSynLogInfo = (
sllTrace,
sllError);
/// may be used to log as Info or Warning event, depending on an Error: boolean
LOG_INFOWARNING: array[boolean] of TSynLogInfo = (
sllInfo,
sllWarning);
/// returns the trimmed text value of a logging level
// - i.e. 'Warning' for sllWarning
function ToText(event: TSynLogInfo): RawUtf8; overload;
/// returns the trimmed text value of a logging levels set
function ToText(events: TSynLogInfos): ShortString; overload;
/// returns the ready-to-be displayed text of a TSynLogInfo value
function ToCaption(event: TSynLogInfo): string; overload;
/// returns the ready-to-be displayed text of a TSynLogFilter value
function ToCaption(filter: TSynLogFilter): string; overload;
/// returns a method event as text, using the .map/.dbg/.mab information if available
function ToText(const Event: TMethod): RawUtf8; overload;
/// retrieve a one-line of text including detailed heap information
// - will use the RTL status entrypoint, or detect mormot.core.fpcx64mm
// and retrieve all its available information
// - as used by TSynLog.AddMemoryStats
function RetrieveMemoryManagerInfo: RawUtf8;
var
/// low-level variable used internally by this unit
// - we use a process-wide giant lock to avoid proper multi-threading of logs
// - do not access this variable in your code: defined here to allow inlining
GlobalThreadLock: TRTLCriticalSection;
/// is set to TRUE before ObjArrayClear(SynLogFile) in unit finalization
// - defined here to avoid unexpected GPF at shutdown
SynLogFileFreeing: boolean;
type
/// class of Exceptions raised by this unit
ESynLogException = class(ESynException);
/// 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+}
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;
{$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 UNICODE}
/// 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/TOrm and sets content as JSON
procedure Log(Level: TSynLogInfo; const aName: RawUtf8; aTypeInfo: PRttiInfo;
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 TDebugFile, 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;
/// 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, EventArchiveLizard 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 TRest.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 ptIdentifiedInOneFile, 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,
ptIdentifiedInOneFile,
ptNoThreadProcess);
/// how stack trace shall be computed during logging
// - stOnlyAPI is the first (and default) value, since manual stack makes
// unexpected detections, and was reported as very slow on Windows 11
// - on FPC, these values are ignored, because RTL CaptureBacktrace() is used
TSynLogStackTraceUse = (
stOnlyAPI,
stManualAndAPI,
stOnlyManual);
/// how file existing shall be handled during logging
TSynLogExistsAction = (
acOverwrite,
acAppend);
{$ifndef NOEXCEPTIONINTERCEPT}
/// callback signature used by TSynLogFamilly.OnBeforeException
// - should return false to log the exception, or true to ignore it
TOnBeforeException = function(const Context: TSynLogExceptionContext;
const ThreadName: RawUtf8): boolean of object;
{$endif NOEXCEPTIONINTERCEPT}
/// 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 ILog: ISynLog;
// ! begin
// ! ILog := TSynLogDB.Enter(self,'MyMethod');
// ! // do some stuff
// ! ILog.Log(sllInfo,'method called');
// ! end; // when ILog is out-of-scope, will log the method leaving
TSynLogFamily = class
protected
fLevel, fLevelStackTrace: TSynLogInfos;
fArchiveAfterDays: integer;
fArchivePath: TFileName;
fOnArchive: TSynLogArchiveEvent;
fOnRotate: TSynLogRotateEvent;
fCustomFileName: TFileName;
fGlobalLog: TSynLog;
fSynLogClass: TSynLogClass;
fDestinationPath: TFileName;
fDefaultExtension: TFileName;
fIdent: integer;
fBufferSize: integer;
fPerThreadLog: TSynLogPerThreadMode;
fIncludeComputerNameInFileName: boolean;
fHighResolutionTimestamp: boolean;
fLocalTimestamp: boolean;
fWithUnitName: boolean;
fWithInstancePointer: boolean;
fNoFile: boolean;
fAutoFlushTimeOut: cardinal;
{$ifndef NOEXCEPTIONINTERCEPT}
fOnBeforeException: TOnBeforeException;
fHandleExceptions: boolean;
{$endif NOEXCEPTIONINTERCEPT}
fStackTraceLevel: byte;
fStackTraceUse: TSynLogStackTraceUse;
fFileExistsAction: TSynLogExistsAction;
{$ifdef OSWINDOWS}
fNoEnvironmentVariable: boolean;
{$endif OSWINDOWS}
fExceptionIgnore: TList;
fEchoToConsole: TSynLogInfos;
fEchoCustom: TOnTextWriterEcho;
fEchoRemoteClient: TObject;
fEchoRemoteEvent: TOnTextWriterEcho;
fEchoRemoteClientOwned: boolean;
fEchoToConsoleUseJournal: boolean;
fEchoToConsoleBackground: boolean;
fEndOfLineCRLF: boolean;
fDestroying: boolean;
fRotateFileCurrent: cardinal;
fRotateFileCount: cardinal;
fRotateFileSize: cardinal;
fRotateFileAtHour: integer;
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;
{$ifndef NOEXCEPTIONINTERCEPT}
function GetExceptionIgnoreCurrentThread: boolean;
procedure SetExceptionIgnoreCurrentThread(aExceptionIgnoreCurrentThread: boolean);
{$endif NOEXCEPTIONINTERCEPT}
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
// - not worth inlining: TSynLog.Add will directly check fGlobalLog
function SynLog: TSynLog;
/// register one object and one echo callback for remote logging
// - aClient is typically a mORMot's TRestHttpClient 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 TOnNotifyThread signature, which can be assigned to
// TSynBackgroundThreadAbstract.OnAfterExecute
// - is called e.g. by TRest.EndCurrentThread
// - just a wrapper around TSynLog.NotifyThreadEnded
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;
{$ifndef NOEXCEPTIONINTERCEPT}
/// 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: TOnBeforeException
read fOnBeforeException write fOnBeforeException;
{$endif NOEXCEPTIONINTERCEPT}
/// 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) - see EchoToConsoleBackground
// - 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;
/// redirect all EchoToConsole logging into the Linux journald service
// - do nothing on Windows or BSD systems
// - such logs can be exported into a format which can be viewed by our
// LogView tool using the following 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;
/// EchoToConsole output is sent from the flush background thread
// - enabled by default on Windows, since its console output is very slow
property EchoToConsoleBackground: boolean
read fEchoToConsoleBackground write fEchoToConsoleBackground;
/// 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 SynLogLookupThreadVar[] 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
// - 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 > 0
// 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 fHighResolutionTimestamp write fHighResolutionTimestamp;
/// 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 fAutoFlushTimeOut write fAutoFlushTimeOut;
{$ifdef OSWINDOWS}
/// 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 OSWINDOWS}
/// 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;
/// 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 (but API may never reach so high)
property StackTraceLevel: byte
read fStackTraceLevel write fStackTraceLevel;
/// how the stack trace shall use only the Windows API
// - default is stOnlyAPI, i.e. use RtlCaptureStackBackTrace() API with
// no manual stack walk (which tends to report wrong calls)
// - on FPC, this property is ignored in favor of RTL CaptureBacktrace()
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;
/// if the method name is local, i.e. shall not be displayed at Leave()
MethodNameLocal: (mnAlways, mnEnter, mnLeave, mnEnterOwnMethodName);
{$ifndef FPC}
/// the caller address, ready to display stack trace dump if needed
Caller: PtrUInt;
{$endif FPC}
/// the high-resolution QueryPerformanceMicroSeconds timestamp at enter time
EnterTimestamp: Int64;
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 recursive calls tracing
Recursion: array of TSynLogThreadRecursion;
/// the associated thread name
// - is probably more complete than CurrentThreadName: TShort31 threadvar
ThreadName: RawUtf8;
end;
// pointer to thread-specific context information
PSynLogThreadContext = ^TSynLogThreadContext;
/// 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 TDebugFile class, for
// stack trace logging for exceptions, sllStackTrace, and Enter/Leave labelling
TSynLog = class(TObject, ISynLog)
// note: don't inherit from TSynInterfacedObject to avoid a method call
protected
fFamily: TSynLogFamily;
fWriter: TJsonWriter;
fWriterEcho: TEchoWriter;
fThreadContext: PSynLogThreadContext;
fThreadID: TThreadID;
fThreadLastHash: integer;
fThreadIndex: integer;
fCurrentLevel: TSynLogInfo;
fInternalFlags: set of (logHeaderWritten, logInitDone);
fDisableRemoteLog: boolean;
{$ifndef NOEXCEPTIONINTERCEPT}
fExceptionIgnoredBackup: boolean;
fExceptionIgnoreThreadVar: PBoolean;
{$endif NOEXCEPTIONINTERCEPT}