7
Classes, SysUtils, FileUtil, types, math, LazLoggerBase, LazClasses;
11
PLazLoggerLogGroup = LazLoggerBase.PLazLoggerLogGroup;
13
{$DEFINE USED_BY_LAZLOGGER}
14
{$I LazLoggerIntf.inc}
17
function DbgStr(const StringWithSpecialChars: string): string; overload;
18
function DbgStr(const StringWithSpecialChars: string; StartPos, Len: PtrInt): string; overload;
19
function DbgStr(const p: PChar; Len: PtrInt): string; overload;
20
function DbgWideStr(const StringWithSpecialChars: widestring): string; overload;
22
function ConvertLineEndings(const s: string): string;
23
procedure ReplaceSubstring(var s: string; StartPos, Count: SizeInt;
24
const Insertion: string);
28
{ TLazLoggerFileHandle }
30
TLazLoggerFileHandle = class
32
FActiveLogText: PText; // may point to stdout
33
FCloseLogFileBetweenWrites: Boolean;
36
FLogTextInUse, FLogTextFailed: Boolean;
39
procedure DoCloseFile;
40
function GetWriteTarget: TLazLoggerWriteTarget;
41
procedure SetCloseLogFileBetweenWrites(AValue: Boolean);
42
procedure SetLogName(AValue: String);
45
destructor Destroy; override;
49
procedure WriteToFile(const s: string); inline;
50
procedure WriteLnToFile(const s: string); inline;
52
property LogName: String read FLogName write SetLogName;
53
property UseStdOut: Boolean read FUseStdOut write FUseStdOut;
54
property CloseLogFileBetweenWrites: Boolean read FCloseLogFileBetweenWrites write SetCloseLogFileBetweenWrites;
55
property WriteTarget: TLazLoggerWriteTarget read GetWriteTarget;
56
property ActiveLogText: PText read FActiveLogText;
61
TLazLoggerFile = class(TLazLoggerWithGroupParam)
63
FFileHandle: TLazLoggerFileHandle;
64
FOnDbgOut: TLazLoggerWriteEvent;
65
FOnDebugLn: TLazLoggerWriteEvent;
68
FEnvironmentForLogFileName: String;
71
FParamForLogFileName: String;
72
FGetLogFileNameDone: Boolean;
74
FDebugNestLvl: Integer;
76
FDebugNestAtBOL: Boolean;
78
function GetFileHandle: TLazLoggerFileHandle;
79
procedure SetEnvironmentForLogFileName(AValue: String);
80
procedure SetFileHandle(AValue: TLazLoggerFileHandle);
81
procedure SetParamForLogFileName(AValue: String);
82
function GetLogFileName: string;
84
// forward to TLazLoggerFileHandle
85
function GetCloseLogFileBetweenWrites: Boolean;
86
function GetLogName: String;
87
function GetUseStdOut: Boolean;
88
procedure SetCloseLogFileBetweenWrites(AValue: Boolean);
89
procedure SetLogName(AValue: String);
90
procedure SetUseStdOut(AValue: Boolean);
92
procedure DoInit; override;
93
procedure DoFinsh; override;
95
procedure IncreaseIndent; overload; override;
96
procedure DecreaseIndent; overload; override;
97
procedure IncreaseIndent(LogGroup: PLazLoggerLogGroup); overload; override;
98
procedure DecreaseIndent(LogGroup: PLazLoggerLogGroup); overload; override;
99
procedure IndentChanged; override;
100
procedure CreateIndent; virtual;
102
procedure DoDbgOut(const s: string); override;
103
procedure DoDebugLn(const s: string); override;
104
procedure DoDebuglnStack(const s: string); override;
106
property FileHandle: TLazLoggerFileHandle read GetFileHandle write SetFileHandle;
109
destructor Destroy; override;
110
procedure Assign(Src: TLazLogger); override;
111
// A param on the commandline, that may contain the name (if not already set)
112
// example/default: --debug-log=
113
property ParamForLogFileName: String read FParamForLogFileName write SetParamForLogFileName;
114
// Environment to specify log file name (* replaced by param(0))
115
// example/default: *_debuglog
116
property EnvironmentForLogFileName: String read FEnvironmentForLogFileName write SetEnvironmentForLogFileName; // "*" will be replaced by appname
118
property OnDebugLn: TLazLoggerWriteEvent read FOnDebugLn write FOnDebugLn;
119
property OnDbgOut: TLazLoggerWriteEvent read FOnDbgOut write FOnDbgOut;
121
// forward to TLazLoggerFileHandle
122
property LogName: String read GetLogName write SetLogName;
123
property UseStdOut: Boolean read GetUseStdOut write SetUseStdOut;
124
property CloseLogFileBetweenWrites: Boolean read GetCloseLogFileBetweenWrites write SetCloseLogFileBetweenWrites;
127
function GetDebugLogger: TLazLoggerFile; inline;
128
procedure SetDebugLogger(ALogger: TLazLoggerFile);
130
property DebugLogger: TLazLoggerFile read GetDebugLogger write SetDebugLogger;
134
{$I LazLoggerImpl.inc}
138
Str_LCL_Debug_File = 'lcldebug.log';
141
(* Creation / Access *)
143
function CreateDebugLogger: TRefCountedObject;
145
Result := TLazLoggerFile.Create;
146
TLazLoggerFile(Result).Assign(GetExistingDebugLogger);
149
function GetDebugLogger: TLazLoggerFile; inline;
151
Result := TLazLoggerFile(LazLoggerBase.DebugLogger);
154
procedure SetDebugLogger(ALogger: TLazLoggerFile);
156
LazLoggerBase.DebugLogger := ALogger;
162
{ TLazLoggerFileHandle }
164
procedure TLazLoggerFileHandle.DoOpenFile;
168
if FActiveLogText <> nil then exit;
170
if (not FLogTextFailed) and (length(FLogName)>0)
172
and (DirPathExists(ExtractFileDir(FLogName)))
178
Assign(FLogText, FLogName);
181
if IOResult <> 0 then
185
Filemode:=fmShareDenyNone;
186
Assign(FLogText, FLogName);
187
if FileExistsUTF8(FLogName) then
192
FActiveLogText := @FLogText;
193
FLogTextInUse := true;
195
FLogTextInUse := false;
196
FActiveLogText := nil;
197
FLogTextFailed := True;
198
// Add extra line ending: a dialog will be shown in windows gui application
199
writeln(StdOut, 'Cannot open file: ', FLogName+LineEnding);
204
if (not FLogTextInUse) and (FUseStdOut) then
206
if not(TextRec(Output).Mode=fmClosed) then
207
FActiveLogText := @Output;
211
procedure TLazLoggerFileHandle.DoCloseFile;
213
if FLogTextInUse then begin
218
FLogTextInUse := false;
220
FActiveLogText := nil;
223
function TLazLoggerFileHandle.GetWriteTarget: TLazLoggerWriteTarget;
226
if FActiveLogText = @Output then
229
if FLogTextInUse then
230
Result := lwtTextFile;
233
procedure TLazLoggerFileHandle.SetCloseLogFileBetweenWrites(AValue: Boolean);
235
if FCloseLogFileBetweenWrites = AValue then Exit;
236
FCloseLogFileBetweenWrites := AValue;
237
if FCloseLogFileBetweenWrites then
241
procedure TLazLoggerFileHandle.SetLogName(AValue: String);
243
if FLogName = AValue then Exit;
248
FLogTextFailed := False;
251
constructor TLazLoggerFileHandle.Create;
253
FLogTextInUse := False;
254
FLogTextFailed := False;
256
FLogName := ExtractFilePath(ParamStr(0)) + Str_LCL_Debug_File;
258
FCloseLogFileBetweenWrites := True;
262
FCloseLogFileBetweenWrites := False;
266
destructor TLazLoggerFileHandle.Destroy;
272
procedure TLazLoggerFileHandle.OpenFile;
274
if not CloseLogFileBetweenWrites then
278
procedure TLazLoggerFileHandle.CloseFile;
281
FLogTextFailed := False;
284
procedure TLazLoggerFileHandle.WriteToFile(const s: string);
287
if FActiveLogText = nil then exit;
289
Write(FActiveLogText^, s);
291
if FCloseLogFileBetweenWrites then
295
procedure TLazLoggerFileHandle.WriteLnToFile(const s: string);
298
if FActiveLogText = nil then exit;
300
WriteLn(FActiveLogText^, s);
302
if FCloseLogFileBetweenWrites then
308
function TLazLoggerFile.GetFileHandle: TLazLoggerFileHandle;
310
if FFileHandle = nil then
311
FFileHandle := TLazLoggerFileHandle.Create;
312
Result := FFileHandle;
315
procedure TLazLoggerFile.SetEnvironmentForLogFileName(AValue: String);
317
if FEnvironmentForLogFileName = AValue then Exit;
319
FGetLogFileNameDone := False;
320
FEnvironmentForLogFileName := AValue;
323
procedure TLazLoggerFile.SetFileHandle(AValue: TLazLoggerFileHandle);
325
if FFileHandle = AValue then Exit;
327
FreeAndNil(FFileHandle);
328
FFileHandle := AValue;
331
procedure TLazLoggerFile.SetParamForLogFileName(AValue: String);
333
if FParamForLogFileName = AValue then Exit;
335
FGetLogFileNameDone := False;
336
FParamForLogFileName := AValue;
339
function TLazLoggerFile.GetCloseLogFileBetweenWrites: Boolean;
341
Result := FileHandle.CloseLogFileBetweenWrites;
344
function TLazLoggerFile.GetLogName: String;
346
Result := FileHandle.LogName;
349
function TLazLoggerFile.GetUseStdOut: Boolean;
351
Result := FileHandle.UseStdOut;
354
procedure TLazLoggerFile.SetCloseLogFileBetweenWrites(AValue: Boolean);
356
FileHandle.CloseLogFileBetweenWrites := AValue;
359
procedure TLazLoggerFile.SetLogName(AValue: String);
361
if FileHandle.LogName = AValue then Exit;
363
FileHandle.LogName := AValue;
366
procedure TLazLoggerFile.SetUseStdOut(AValue: Boolean);
368
FileHandle.UseStdOut := AValue;
371
procedure TLazLoggerFile.DoInit;
376
FDebugNestAtBOL := True;
377
if (LogName = '') and not FGetLogFileNameDone then
378
LogName := GetLogFileName;
383
procedure TLazLoggerFile.DoFinsh;
387
FileHandle.CloseFile;
390
procedure TLazLoggerFile.IncreaseIndent;
396
procedure TLazLoggerFile.DecreaseIndent;
398
if not FDebugNestAtBOL then DebugLn;
400
if FDebugNestLvl > 0 then
405
procedure TLazLoggerFile.IncreaseIndent(LogGroup: PLazLoggerLogGroup);
407
if (LogGroup <> nil) then begin
408
if (not LogGroup^.Enabled) then exit;
409
inc(LogGroup^.FOpenedIndents);
416
procedure TLazLoggerFile.DecreaseIndent(LogGroup: PLazLoggerLogGroup);
418
if (LogGroup <> nil) then begin
419
// close what was opened, even if now disabled
420
// only close, if opened by this group
421
if (LogGroup^.FOpenedIndents <= 0) then exit;
422
dec(LogGroup^.FOpenedIndents);
429
procedure TLazLoggerFile.IndentChanged;
434
procedure TLazLoggerFile.CreateIndent;
439
NewLen := FDebugNestLvl * NestLvlIndent;
440
if NewLen < 0 then NewLen := 0;
441
if (NewLen >= MaxNestPrefixLen) then begin
442
s := IntToStr(FDebugNestLvl);
443
NewLen := MaxNestPrefixLen - Length(s);
449
if NewLen <> Length(FDebugIndent) then
450
FDebugIndent := s + StringOfChar(' ', NewLen);
453
procedure TLazLoggerFile.DoDbgOut(const s: string);
457
if not IsInitialized then Init;
459
if OnDbgOut <> nil then
462
if FDebugNestAtBOL and (s <> '') then
463
OnDbgOut(Self, FDebugIndent + s, Handled)
465
OnDbgOut(Self, s, Handled);
470
if OnWidgetSetDbgOut <> nil then
473
if FDebugNestAtBOL and (s <> '') then
474
OnWidgetSetDbgOut(Self, FDebugIndent + s, Handled,
475
FileHandle.WriteTarget, FileHandle.ActiveLogText)
477
OnWidgetSetDbgOut(Self, s, Handled, FileHandle.WriteTarget, FileHandle.ActiveLogText);
482
if FDebugNestAtBOL and (s <> '') then
483
FileHandle.WriteToFile(FDebugIndent + s)
485
FileHandle.WriteToFile(s);
486
FDebugNestAtBOL := (s = '') or (s[length(s)] in [#10,#13]);
489
procedure TLazLoggerFile.DoDebugLn(const s: string);
493
if not IsInitialized then Init;
495
if OnDebugLn <> nil then
498
if FDebugNestAtBOL and (s <> '') then
499
OnDebugLn(Self, FDebugIndent + s, Handled)
501
OnDebugLn(Self, s, Handled);
506
if OnWidgetSetDebugLn <> nil then
509
if FDebugNestAtBOL and (s <> '') then
510
OnWidgetSetDebugLn(Self, FDebugIndent + s, Handled,
511
FileHandle.WriteTarget, FileHandle.ActiveLogText)
513
OnWidgetSetDebugLn(Self, s, Handled, FileHandle.WriteTarget, FileHandle.ActiveLogText);
518
if FDebugNestAtBOL and (s <> '') then
519
FileHandle.WriteLnToFile(FDebugIndent + ConvertLineEndings(s))
521
FileHandle.WriteLnToFile(ConvertLineEndings(s));
522
FDebugNestAtBOL := True;
525
procedure TLazLoggerFile.DoDebuglnStack(const s: string);
528
FileHandle.DoOpenFile;
529
if FileHandle.FActiveLogText = nil then exit;
531
Dump_Stack(FileHandle.FActiveLogText^, get_frame);
533
if CloseLogFileBetweenWrites then
534
FileHandle.DoCloseFile;
537
constructor TLazLoggerFile.Create;
543
FParamForLogFileName := '';
544
FEnvironmentForLogFileName := '';
546
FParamForLogFileName := '--debug-log=';
547
FEnvironmentForLogFileName := '*_debuglog';
551
destructor TLazLoggerFile.Destroy;
554
FreeAndNil(FFileHandle);
557
procedure TLazLoggerFile.Assign(Src: TLazLogger);
559
inherited Assign(Src);
560
if (Src <> nil) and (Src is TLazLoggerFile) then begin
561
FOnDbgOut := TLazLoggerFile(Src).FOnDbgOut;
562
FOnDebugLn := TLazLoggerFile(Src).FOnDebugLn;;
564
FEnvironmentForLogFileName := TLazLoggerFile(Src).FEnvironmentForLogFileName;
565
FParamForLogFileName := TLazLoggerFile(Src).FParamForLogFileName;
566
FGetLogFileNameDone := TLazLoggerFile(Src).FGetLogFileNameDone;
568
LogName := TLazLoggerFile(Src).LogName;
569
UseStdOut := TLazLoggerFile(Src).UseStdOut;
570
CloseLogFileBetweenWrites := TLazLoggerFile(Src).CloseLogFileBetweenWrites;
574
function TLazLoggerFile.GetLogFileName: string;
579
FGetLogFileNameDone := True;
580
if FParamForLogFileName <> '' then begin
581
// first try to find the log file name in the command line parameters
582
Result := GetParamByName(FParamForLogFileName, 0);
584
if FEnvironmentForLogFileName <> '' then begin;
585
// if not found yet, then try to find in the environment variables
586
if (length(result)=0) then begin
587
EnvVarName:= ChangeFileExt(ExtractFileName(ParamStrUTF8(0)),'') + FEnvironmentForLogFileName;
588
Result := GetEnvironmentVariableUTF8(EnvVarName);
591
if (length(result)>0) then
592
Result := ExpandFileNameUTF8(Result);
596
function DbgStr(const StringWithSpecialChars: string): string;
602
Result:=StringWithSpecialChars;
604
while (i<=length(Result)) do begin
608
s:='#'+HexStr(ord(Result[i]),2);
609
// Note: do not use copy, fpc might change broken UTF-8 characters to '?'
611
SetLength(Result,length(Result)-1+length(s));
613
system.Move(Result[i+1],Result[i+length(s)],l);
614
system.Move(s[1],Result[i],length(s));
620
function DbgStr(const StringWithSpecialChars: string; StartPos, Len: PtrInt
623
Result:=dbgstr(copy(StringWithSpecialChars,StartPos,Len));
626
function DbgStr(const p: PChar; Len: PtrInt): string;
628
Hex: array[0..15] of char='0123456789ABCDEF';
636
if (p=nil) or (p^=#0) or (Len<=0) then exit('');
640
while Src^<>#0 do begin
642
if Src^ in [' '..#126] then
646
if UsedLen>=Len then break;
649
SetLength(Result,ResultLen);
652
while UsedLen>0 do begin
655
if c in [' '..#126] then begin
661
Dest^:=Hex[ord(c) shr 4];
663
Dest^:=Hex[ord(c) and $f];
670
function DbgWideStr(const StringWithSpecialChars: widestring): string;
677
SetLength(Result,length(StringWithSpecialChars));
680
while SrcPos<=length(StringWithSpecialChars) do begin
681
i:=ord(StringWithSpecialChars[SrcPos]);
685
Result[DestPos]:=chr(i);
692
Result:=copy(Result,1,DestPos-1)+s+copy(Result,DestPos+1,length(Result));
693
inc(DestPos,length(s));
698
function ConvertLineEndings(const s: string): string;
701
EndingStart: LongInt;
705
while (i<=length(Result)) do begin
706
if Result[i] in [#10,#13] then begin
709
if (i<=length(Result)) and (Result[i] in [#10,#13])
710
and (Result[i]<>Result[i-1]) then begin
713
if (length(LineEnding)<>i-EndingStart)
714
or (LineEnding<>copy(Result,EndingStart,length(LineEnding))) then begin
715
// line end differs => replace with current LineEnding
717
copy(Result,1,EndingStart-1)+LineEnding+copy(Result,i,length(Result));
718
i:=EndingStart+length(LineEnding);
725
procedure ReplaceSubstring(var s: string; StartPos, Count: SizeInt;
726
const Insertion: string);
729
InsertionLen: SizeInt;
735
if StartPos>SLen then
737
if StartPos<1 then StartPos:=1;
738
if Count<0 then Count:=0;
739
MaxCount:=SLen-StartPos+1;
740
if Count>MaxCount then
742
InsertionLen:=length(Insertion);
743
if (Count=0) and (InsertionLen=0) then
744
exit; // nothing to do
745
if (Count=InsertionLen) then begin
746
if CompareMem(PByte(s)+StartPos-1,Pointer(Insertion),Count) then
747
// already the same content
751
RestLen:=SLen-StartPos-Count+1;
752
if InsertionLen<Count then begin
754
if RestLen>0 then begin
756
p:=PByte(s)+StartPos-1;
757
System.Move((p+Count)^,(p+InsertionLen)^,RestLen);
759
Setlength(s,SLen-Count+InsertionLen);
762
Setlength(s,SLen-Count+InsertionLen);
763
if RestLen>0 then begin
764
p:=PByte(s)+StartPos-1;
765
System.Move((p+Count)^,(p+InsertionLen)^,RestLen);
769
if InsertionLen>0 then
770
System.Move(PByte(Insertion)^,(PByte(s)+StartPos-1)^,InsertionLen);
774
LazDebugLoggerCreator := @CreateDebugLogger;