Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appender enhancements #91

Open
wants to merge 2 commits into
base: v2.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 112 additions & 43 deletions LoggerPro.FileAppender.pas
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,34 @@ interface
Do not use this class directly, but one of TLoggerProFileAppender or TLoggerProSimpleFileAppender.
Check the sample @code(file_appender.dproj)
}

TLoggerProStreamWriter = class(TStreamWriter)
private
FFileCreationDate: TDateTime;
public
property FileCreationDate: TDateTime read FFileCreationDate write FFileCreationDate;
end;

TLoggerProFileAppenderBase = class(TLoggerProAppenderBase)
private
fMaxBackupFileCount: Integer;
fMaxBackupFileCountLength : Integer;
fMaxFileSizeInKiloByte: Integer;
fLogFileNameFormat: string;
fLogsFolder: string;
fEncoding: TEncoding;
fDailyRotate : Boolean;
function RotateLogNeeded(aLogWriter: TStreamWriter): Boolean;
function CreateWriter(const aFileName: string): TStreamWriter;
function GetFileDate(const aFileName: string): TDateTime;
function GetFileDateFormatted(const aFileName: string): string;
function FindLogFileByNumber(const aTag: string; const aFileNumber: Integer): string;
procedure RetryMove(const aFileSrc, aFileDest: string);
protected
procedure CheckLogFileNameFormat(const LogFileNameFormat: String); virtual;
procedure EmitStartRotateLogItem(aWriter: TStreamWriter); virtual;
procedure EmitEndRotateLogItem(aWriter: TStreamWriter); virtual;
function GetLogFileName(const aTag: string; const aFileNumber: Integer): string; virtual;
function GetLogFileName(const aTag, aFileDate: string; const aFileNumber: Integer): string; virtual;
procedure WriteToStream(const aStreamWriter: TStreamWriter; const aValue: string); inline;
procedure RotateFile(const aLogTag: string; out aNewFileName: string); virtual;
procedure InternalWriteLog(const aStreamWriter: TStreamWriter; const aLogItem: TLogItem);
Expand All @@ -92,6 +106,7 @@ TLoggerProFileAppenderBase = class(TLoggerProAppenderBase)
}
DEFAULT_FILENAME_FORMAT = '{module}.{number}.{tag}.log';
DEFAULT_FILENAME_FORMAT_WITH_PID = '{module}.{number}.{pid}.{tag}.log';
DEFAULT_FILENAME_DAILY_ROTATE_FORMAT = '{module}.{date}.{number}.{tag}.log';
{ @abstract(Defines number of log file set to maintain during logs rotation) }
DEFAULT_MAX_BACKUP_FILE_COUNT = 5;
{ @abstract(Defines the max size of each log file)
Expand All @@ -101,14 +116,14 @@ TLoggerProFileAppenderBase = class(TLoggerProAppenderBase)
RETRY_DELAY = 200;
{ @abstract(How many times do we have to retry if the file is locked?. }
RETRY_COUNT = 5;
constructor Create(
aMaxBackupFileCount: Integer = TLoggerProFileAppenderBase.DEFAULT_MAX_BACKUP_FILE_COUNT;
aMaxFileSizeInKiloByte: Integer = TLoggerProFileAppenderBase.DEFAULT_MAX_FILE_SIZE_KB;
aLogsFolder: string = '';
aLogFileNameFormat: string = TLoggerProFileAppenderBase.DEFAULT_FILENAME_FORMAT;
aLogItemRenderer: ILogItemRenderer = nil;
aEncoding: TEncoding = nil);
reintroduce; virtual;
DEFAULT_DAILYROTATE_FILEDATE_FORMAT = 'yyyy-mm-dd';
constructor Create(aMaxBackupFileCount: Integer = TLoggerProFileAppenderBase.DEFAULT_MAX_BACKUP_FILE_COUNT;
aMaxFileSizeInKiloByte: Integer = TLoggerProFileAppenderBase.DEFAULT_MAX_FILE_SIZE_KB;
aLogsFolder: string = '';
aLogFileNameFormat: string = TLoggerProFileAppenderBase.DEFAULT_FILENAME_FORMAT;
aLogItemRenderer: ILogItemRenderer = nil;
aEncoding: TEncoding = nil);
reintroduce; virtual;
procedure Setup; override;
end;

Expand All @@ -120,7 +135,7 @@ TLoggerProFileAppender = class(TLoggerProFileAppenderBase)
private
fWritersDictionary: TObjectDictionary<string, TStreamWriter>;
procedure AddWriter(const aLogTag: string; var aWriter: TStreamWriter; var aLogFileName: string);
procedure RotateLog(const aLogTag: string; aWriter: TStreamWriter);
procedure RotateLog(const aLogTag: string; var aWriter: TStreamWriter);
public
procedure Setup; override;
procedure TearDown; override;
Expand Down Expand Up @@ -163,6 +178,7 @@ implementation
System.IOUtils,
System.StrUtils,
System.Math,
System.DateUtils,
idGlobal
{$IF Defined(Android), System.SysUtils}
,Androidapi.Helpers
Expand Down Expand Up @@ -199,12 +215,14 @@ procedure TLoggerProFileAppenderBase.CheckLogFileNameFormat(const LogFileNameFor

{ TLoggerProFileAppenderBase }

function TLoggerProFileAppenderBase.GetLogFileName(const aTag: string; const aFileNumber: Integer): string;
function TLoggerProFileAppenderBase.GetLogFileName(const aTag, aFileDate:
string; const aFileNumber: Integer): string;
var
// lExt: string;
lModuleName: string;
lPath: string;
lFormat: string;
lFileDate: string;
begin
{$IF Defined(Android)}
lModuleName := TAndroidHelper.ApplicationTitle.Replace(' ', '_', [rfReplaceAll]);
Expand All @@ -218,9 +236,14 @@ function TLoggerProFileAppenderBase.GetLogFileName(const aTag: string; const aFi
lFormat := fLogFileNameFormat;

lPath := fLogsFolder;
if aFileDate = '' then
lFileDate := '0'
else
lFileDate := aFileDate;
lFormat := lFormat
.Replace('{module}', lModuleName, [rfReplaceAll])
.Replace('{number}', aFileNumber.ToString.PadLeft(2,'0') , [rfReplaceAll])
.Replace('{date}', lFileDate, [rfReplaceAll])
.Replace('{number}', aFileNumber.ToString.PadLeft(fMaxBackupFileCountLength,'0') , [rfReplaceAll])
.Replace('{tag}', aTag, [rfReplaceAll])
.Replace('{pid}', CurrentProcessId.ToString.PadLeft(8,'0'), [rfReplaceAll]);
Result := TPath.Combine(lPath, lFormat);
Expand Down Expand Up @@ -290,37 +313,39 @@ procedure TLoggerProFileAppenderBase.RotateFile(const aLogTag: string; out aNewF
I: Integer;
lCurrentFileName: string;
begin
aNewFileName := GetLogFileName(aLogTag, 0);
aNewFileName := GetLogFileName(aLogTag, '', 0);
// remove the last file of backup set
lRenamedFile := GetLogFileName(aLogTag, fMaxBackupFileCount - 1);
if TFile.Exists(lRenamedFile) then
TFile.Delete(lRenamedFile);
lCurrentFileName := FindLogFileByNumber(aLogTag, fMaxBackupFileCount - 1);
if lCurrentFileName <> '' then
TFile.Delete(lCurrentFileName);
// shift the files names
for I := fMaxBackupFileCount - 1 downto 1 do
begin
lCurrentFileName := GetLogFileName(aLogTag, I);
lRenamedFile := GetLogFileName(aLogTag, I + 1);
if TFile.Exists(lCurrentFileName) then
RetryMove(lCurrentFileName, lRenamedFile);
lCurrentFileName := FindLogFileByNumber(aLogTag, I);
if lCurrentFileName = '' then
Continue;
lRenamedFile := GetLogFileName(aLogTag, GetFileDateFormatted(lCurrentFileName), I + 1);
RetryMove(lCurrentFileName, lRenamedFile);
end;
lRenamedFile := GetLogFileName(aLogTag, 1);
lRenamedFile := GetLogFileName(aLogTag, GetFileDateFormatted(aNewFileName), 1);
RetryMove(aNewFileName, lRenamedFile);
end;

constructor TLoggerProFileAppenderBase.Create(
aMaxBackupFileCount: Integer;
aMaxFileSizeInKiloByte: Integer;
aLogsFolder: string;
aLogFileNameFormat: string;
aLogItemRenderer: ILogItemRenderer;
aEncoding: TEncoding);
constructor TLoggerProFileAppenderBase.Create(aMaxBackupFileCount: Integer = TLoggerProFileAppenderBase.DEFAULT_MAX_BACKUP_FILE_COUNT;
aMaxFileSizeInKiloByte: Integer = TLoggerProFileAppenderBase.DEFAULT_MAX_FILE_SIZE_KB;
aLogsFolder: string = '';
aLogFileNameFormat: string = TLoggerProFileAppenderBase.DEFAULT_FILENAME_FORMAT;
aLogItemRenderer: ILogItemRenderer = nil;
aEncoding: TEncoding = nil);
begin
inherited Create(aLogItemRenderer);
fLogsFolder := aLogsFolder;
fMaxBackupFileCount:= Max(1, aMaxBackupFileCount);
fMaxBackupFileCountLength := Max(2, (fMaxBackupFileCount-1).ToString.Length);
fMaxFileSizeInKiloByte := aMaxFileSizeInKiloByte;
CheckLogFileNameFormat(aLogFileNameFormat);
fLogFileNameFormat := aLogFileNameFormat;
fDailyRotate := fLogFileNameFormat.IndexOf('{date}') >= 0;
if Assigned(aEncoding) then
fEncoding := aEncoding
else
Expand Down Expand Up @@ -348,9 +373,10 @@ function TLoggerProFileAppenderBase.CreateWriter(const aFileName: string): TStre
lFileStream := TFileStream.Create(aFileName, lFileAccessMode);
try
lFileStream.Seek(0, TSeekOrigin.soEnd);
Result := TStreamWriter.Create(lFileStream, fEncoding, 32);
Result := TLoggerProStreamWriter.Create(lFileStream, fEncoding, 32);
Result.AutoFlush := true;
Result.OwnStream;
TLoggerProStreamWriter(Result).FileCreationDate := GetFileDate(aFileName);
Break;
except
lFileStream.Free;
Expand All @@ -374,11 +400,22 @@ function TLoggerProFileAppenderBase.CreateWriter(const aFileName: string): TStre

procedure TLoggerProFileAppender.AddWriter(const aLogTag:string; var aWriter: TStreamWriter; var aLogFileName: string);
begin
aLogFileName := GetLogFileName(aLogTag, 0);
aLogFileName := GetLogFileName(aLogTag, '',0);
aWriter := CreateWriter(aLogFileName);
fWritersDictionary.Add(aLogTag, aWriter);
end;

function TLoggerProFileAppenderBase.RotateLogNeeded(aLogWriter: TStreamWriter):
Boolean;
begin
if ((fMaxFileSizeInKiloByte> 0) and (aLogWriter.BaseStream.Size > fMaxFileSizeInKiloByte * 1024))
or ((fDailyRotate) and (not TLoggerProStreamWriter(aLogWriter).FileCreationDate.IsSameDay(Now())))
then
Result := True
else
Result := False;
end;

procedure TLoggerProFileAppenderBase.EmitEndRotateLogItem(aWriter: TStreamWriter);
begin
WriteToStream(aWriter, '#[ROTATE LOG ' + datetimetostr(Now, FormatSettings) + ']');
Expand All @@ -389,19 +426,54 @@ procedure TLoggerProFileAppenderBase.EmitStartRotateLogItem(aWriter: TStreamWrit
WriteToStream(aWriter, '#[START LOG ' + datetimetostr(Now, FormatSettings) + ']');
end;

procedure TLoggerProFileAppender.RotateLog(const aLogTag: string; aWriter: TStreamWriter);
function TLoggerProFileAppenderBase.GetFileDate(const aFileName: string): TDateTime;
begin
Result := 0;
if not fDailyRotate then
Exit;
try
Result := TFile.GetCreationTime (aFileName);
except
Result := 0;
end;
end;

function TLoggerProFileAppenderBase.GetFileDateFormatted(const aFileName: string): string;
var Date : tDateTime;
begin
Result := '';
Date := GetFileDate(aFileName);
if Date <= 0 then
Exit;
Result := FormatDateTime (DEFAULT_DAILYROTATE_FILEDATE_FORMAT, Date);
end;

function TLoggerProFileAppenderBase.FindLogFileByNumber(const aTag: string; const aFileNumber: Integer): string;
var
SearchRec: TSearchRec;
begin
Result := GetLogFileName(aTag, '*', aFileNumber);
if findfirst (Result, faAnyFile, SearchRec) = 0 then
Result := TPath.GetDirectoryName (Result) + PathDelim + SearchRec.Name
else
Result := '';
FindClose (SearchRec);
if Result <> '' then
Exit;
end;

procedure TLoggerProFileAppender.RotateLog(const aLogTag: string; var aWriter:
TStreamWriter);
var
lLogFileName: string;
begin
EmitEndRotateLogItem(aWriter);
//WriteToStream(aWriter, '#[ROTATE LOG ' + datetimetostr(Now, FormatSettings) + ']');
// remove the writer during rename
fWritersDictionary.Remove(aLogTag);
RotateFile(aLogTag, lLogFileName);
// re-create the writer
AddWriter(aLogTag, aWriter, lLogFileName);
EmitStartRotateLogItem(aWriter);
//WriteToStream(aWriter, '#[START LOG ' + datetimetostr(Now, FormatSettings) + ']');
end;

procedure TLoggerProFileAppender.Setup;
Expand All @@ -426,12 +498,11 @@ procedure TLoggerProFileAppender.WriteLog(const aLogItem: TLogItem);
AddWriter(aLogItem.LogTag, lWriter, lLogFileName);
end;

if RotateLogNeeded(lWriter) then
RotateLog(aLogItem.LogTag, lWriter);

InternalWriteLog(lWriter, aLogItem);

if lWriter.BaseStream.Size > fMaxFileSizeInKiloByte * 1024 then
begin
RotateLog(aLogItem.LogTag, lWriter);
end;
end;

{ TLoggerProSimpleFileAppender }
Expand Down Expand Up @@ -473,14 +544,14 @@ procedure TLoggerProSimpleFileAppender.RotateLog;
fFileWriter.Free;
RotateFile('', lLogFileName);
// re-create the writer
fFileWriter := CreateWriter(GetLogFileName('', 0));
fFileWriter := CreateWriter(GetLogFileName('', '', 0));
EmitStartRotateLogItem(fFileWriter);
end;

procedure TLoggerProSimpleFileAppender.Setup;
begin
inherited;
fFileWriter := CreateWriter(GetLogFileName('', 0));
fFileWriter := CreateWriter(GetLogFileName('', '', 0));
end;

procedure TLoggerProSimpleFileAppender.TearDown;
Expand All @@ -491,11 +562,9 @@ procedure TLoggerProSimpleFileAppender.TearDown;

procedure TLoggerProSimpleFileAppender.WriteLog(const aLogItem: TLogItem);
begin
InternalWriteLog(fFileWriter, aLogItem);
if fFileWriter.BaseStream.Size > fMaxFileSizeInKiloByte * 1024 then
begin
if RotateLogNeeded(fFileWriter) then
RotateLog;
end;
InternalWriteLog(fFileWriter, aLogItem);
end;

end.
Expand Down
4 changes: 2 additions & 2 deletions LoggerPro.JSONLFileAppender.pas
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface
}
TLoggerProJSONLFileAppender = class(TLoggerProSimpleFileAppender)
protected
function GetLogFileName(const aTag: string; const aFileNumber: Integer): string; override;
function GetLogFileName(const aTag, aFileDate: string; const aFileNumber: Integer): string; override;
procedure EmitStartRotateLogItem(aWriter: TStreamWriter); override;
procedure EmitEndRotateLogItem(aWriter: TStreamWriter); override;
public
Expand Down Expand Up @@ -102,7 +102,7 @@ procedure TLoggerProJSONLFileAppender.EmitStartRotateLogItem(aWriter: TStreamWri
// do nothing
end;

function TLoggerProJSONLFileAppender.GetLogFileName(const aTag: string; const aFileNumber: Integer): string;
function TLoggerProJSONLFileAppender.GetLogFileName(const aTag, aFileDate: string; const aFileNumber: Integer): string;
var
lOrigFName, lOrigExt: string;
begin
Expand Down
Loading