diff --git a/.gitignore b/.gitignore
index bfdd04b..54f2f6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ Test/bin/
*.tvsconfig
*.local
*.identcache
+*.dcu
diff --git a/Packages/X2LogDXE2.dpk b/Packages/X2LogDXE2.dpk
index 7212a7e..541bb7a 100644
--- a/Packages/X2LogDXE2.dpk
+++ b/Packages/X2LogDXE2.dpk
@@ -52,7 +52,11 @@ contains
X2Log.Util.Stream in '..\X2Log.Util.Stream.pas',
X2Log.Translations.Dutch in '..\X2Log.Translations.Dutch.pas',
X2Log.Decorator in '..\X2Log.Decorator.pas',
- X2Log.Observer.RollingLogFile in '..\X2Log.Observer.RollingLogFile.pas';
+ X2Log.Observer.RollingLogFile in '..\X2Log.Observer.RollingLogFile.pas',
+ X2Log.TextFormatter.Default in '..\X2Log.TextFormatter.Default.pas',
+ X2Log.TextFormatter.Intf in '..\X2Log.TextFormatter.Intf.pas',
+ X2Log.Intf.NamedPipe in '..\X2Log.Intf.NamedPipe.pas';
end.
+
diff --git a/Packages/X2LogDXE2.dproj b/Packages/X2LogDXE2.dproj
index 8146f87..eb66afa 100644
--- a/Packages/X2LogDXE2.dproj
+++ b/Packages/X2LogDXE2.dproj
@@ -109,6 +109,9 @@
+
+
+
Base
diff --git a/Packages/X2LogDXE2Group.groupproj b/Packages/X2LogDXE2Group.groupproj
index a68d75e..9c46d93 100644
--- a/Packages/X2LogDXE2Group.groupproj
+++ b/Packages/X2LogDXE2Group.groupproj
@@ -12,6 +12,9 @@
+
+
+
Default.Personality.12
@@ -47,14 +50,23 @@
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
diff --git a/Packages/X2LogJsonDXE2.dpk b/Packages/X2LogJsonDXE2.dpk
new file mode 100644
index 0000000..ba6e72a
--- /dev/null
+++ b/Packages/X2LogJsonDXE2.dpk
@@ -0,0 +1,39 @@
+package X2LogJsonDXE2;
+
+{$R *.res}
+{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
+{$ALIGN 8}
+{$ASSERTIONS ON}
+{$BOOLEVAL OFF}
+{$DEBUGINFO ON}
+{$EXTENDEDSYNTAX ON}
+{$IMPORTEDDATA ON}
+{$IOCHECKS ON}
+{$LOCALSYMBOLS ON}
+{$LONGSTRINGS ON}
+{$OPENSTRINGS ON}
+{$OPTIMIZATION OFF}
+{$OVERFLOWCHECKS OFF}
+{$RANGECHECKS OFF}
+{$REFERENCEINFO ON}
+{$SAFEDIVIDE OFF}
+{$STACKFRAMES ON}
+{$TYPEDADDRESS OFF}
+{$VARSTRINGCHECKS ON}
+{$WRITEABLECONST OFF}
+{$MINENUMSIZE 1}
+{$IMAGEBASE $400000}
+{$DEFINE DEBUG}
+{$DEFINE $(DELPHIBIN64)}
+{$ENDIF IMPLICITBUILDING}
+{$IMPLICITBUILD ON}
+
+requires
+ rtl,
+ X2LogDXE2;
+
+contains
+ X2Log.TextFormatter.Json in '..\X2Log.TextFormatter.Json.pas';
+
+end.
+
diff --git a/Packages/X2LogJsonDXE2.dproj b/Packages/X2LogJsonDXE2.dproj
new file mode 100644
index 0000000..6d26e67
--- /dev/null
+++ b/Packages/X2LogJsonDXE2.dproj
@@ -0,0 +1,187 @@
+
+
+ {5742A326-C831-4737-AA01-900CC5C18CCA}
+ X2LogJsonDXE2.dpk
+ 13.4
+ None
+ True
+ Debug
+ Win32
+ 3
+ Package
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ 1043
+ CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=
+ All
+ true
+ true
+ System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)
+ .\$(Platform)\$(Config)
+ .\$(Platform)\$(Config)
+ false
+ false
+ false
+ false
+ false
+
+
+ true
+ $(DELPHIBIN64);$(DCC_Define)
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)
+ $(DELPHIBIN64)
+ $(DELPHILIB64)
+ 1033
+
+
+ $(DELPHIBIN);$(DCC_Define)
+ $(DELPHILIB)
+ $(DELPHIBIN)
+ true
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ 1033
+
+
+ DEBUG;$(DCC_Define)
+ false
+ true
+ true
+ true
+
+
+ true
+ 1033
+
+
+ true
+ false
+ 1033
+ false
+
+
+ false
+ RELEASE;$(DCC_Define)
+ 0
+ false
+
+
+ true
+ 1033
+
+
+ true
+ 1033
+
+
+
+ MainSource
+
+
+
+
+
+ Cfg_2
+ Base
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+
+ Delphi.Personality.12
+ Package
+
+
+
+
+ True
+ False
+ 1
+ 0
+ 0
+ 0
+ False
+ False
+ False
+ False
+ False
+ 1043
+ 1252
+
+
+
+
+ 1.0.0.0
+
+
+
+
+
+ 1.0.0.0
+
+
+
+
+
+ True
+ True
+
+
+ 12
+
+
+
+
diff --git a/Packages/X2LogJsonDXE2.res b/Packages/X2LogJsonDXE2.res
new file mode 100644
index 0000000..27efe49
Binary files /dev/null and b/Packages/X2LogJsonDXE2.res differ
diff --git a/Test/X2LogTest.dpr b/Test/X2LogTest.dpr
index a986388..080f46b 100644
--- a/Test/X2LogTest.dpr
+++ b/Test/X2LogTest.dpr
@@ -25,7 +25,10 @@ uses
X2Log.Util.Stream in '..\X2Log.Util.Stream.pas',
X2Log.Decorator in '..\X2Log.Decorator.pas',
X2Log.Observer.RollingLogFile in '..\X2Log.Observer.RollingLogFile.pas',
- X2Log.Intf.NamedPipe in '..\X2Log.Intf.NamedPipe.pas';
+ X2Log.Intf.NamedPipe in '..\X2Log.Intf.NamedPipe.pas',
+ X2Log.TextFormatter.Intf in '..\X2Log.TextFormatter.Intf.pas',
+ X2Log.TextFormatter.Default in '..\X2Log.TextFormatter.Default.pas',
+ X2Log.TextFormatter.Json in '..\X2Log.TextFormatter.Json.pas';
{$R *.res}
diff --git a/Test/X2LogTest.dproj b/Test/X2LogTest.dproj
index 2fc63b0..1666038 100644
--- a/Test/X2LogTest.dproj
+++ b/Test/X2LogTest.dproj
@@ -197,6 +197,9 @@
+
+
+
RCDATA
GraphicDetails
diff --git a/Test/source/MainFrm.dfm b/Test/source/MainFrm.dfm
index 61657c1..34bffdc 100644
--- a/Test/source/MainFrm.dfm
+++ b/Test/source/MainFrm.dfm
@@ -26,7 +26,7 @@ object MainForm: TMainForm
Margins.Top = 8
Margins.Right = 8
Margins.Bottom = 8
- ActivePage = tsNamedPipe
+ ActivePage = tsRollingFile
Align = alClient
Images = ilsObservers
TabOrder = 0
@@ -131,6 +131,32 @@ object MainForm: TMainForm
Caption = 'Absolute path'
TabOrder = 5
end
+ object pnlFileTextFormatter: TPanel
+ Left = 88
+ Top = 176
+ Width = 532
+ Height = 49
+ BevelOuter = bvNone
+ TabOrder = 6
+ object rbFileTextFormatterDefault: TRadioButton
+ Left = 0
+ Top = 0
+ Width = 113
+ Height = 17
+ Caption = 'Default'
+ Checked = True
+ TabOrder = 0
+ TabStop = True
+ end
+ object rbFileTextFormatterJson: TRadioButton
+ Left = 0
+ Top = 23
+ Width = 113
+ Height = 17
+ Caption = 'Json'
+ TabOrder = 1
+ end
+ end
end
object tsRollingFile: TTabSheet
Caption = 'Rolling File'
@@ -212,6 +238,32 @@ object MainForm: TMainForm
TabOrder = 6
Text = '7'
end
+ object pnlRollingFileTextFormatter: TPanel
+ Left = 88
+ Top = 224
+ Width = 532
+ Height = 49
+ BevelOuter = bvNone
+ TabOrder = 7
+ object rbRollingFileTextFormatterDefault: TRadioButton
+ Left = 0
+ Top = 0
+ Width = 113
+ Height = 17
+ Caption = 'Default'
+ Checked = True
+ TabOrder = 0
+ TabStop = True
+ end
+ object rbRollingFileTextFormatterJson: TRadioButton
+ Left = 0
+ Top = 23
+ Width = 113
+ Height = 17
+ Caption = 'Json'
+ TabOrder = 1
+ end
+ end
end
object tsNamedPipe: TTabSheet
Caption = 'Named Pipe'
@@ -609,7 +661,7 @@ object MainForm: TMainForm
Left = 552
Top = 176
Bitmap = {
- 494C01010200140060000C000C00FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
+ 494C01010200140068000C000C00FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000300000000C00000001002000000000000009
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/Test/source/MainFrm.pas b/Test/source/MainFrm.pas
index e096835..60e65b2 100644
--- a/Test/source/MainFrm.pas
+++ b/Test/source/MainFrm.pas
@@ -81,6 +81,12 @@ type
edtRollingDays: TEdit;
tsStructured: TTabSheet;
btnValueTypes: TButton;
+ pnlFileTextFormatter: TPanel;
+ rbFileTextFormatterDefault: TRadioButton;
+ rbFileTextFormatterJson: TRadioButton;
+ pnlRollingFileTextFormatter: TPanel;
+ rbRollingFileTextFormatterDefault: TRadioButton;
+ rbRollingFileTextFormatterJson: TRadioButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
@@ -136,6 +142,8 @@ uses
X2Log.Observer.MonitorForm,
X2Log.Observer.NamedPipe,
X2Log.Observer.RollingLogFile,
+ X2Log.TextFormatter.Intf,
+ X2Log.TextFormatter.Json,
X2Log.Global;
@@ -339,15 +347,22 @@ end;
procedure TMainForm.btnFileStartClick(Sender: TObject);
+var
+ textFormatter: IX2LogTextFormatter;
+
begin
if not Assigned(FFileObserver) then
begin
+ textFormatter := nil;
+ if rbFileTextFormatterJson.Checked then
+ textFormatter := TX2LogJsonTextFormatter.Create;
+
if rbProgramData.Checked then
- FFileObserver := TX2LogFileObserver.CreateInProgramData(edtFilename.Text)
+ FFileObserver := TX2LogFileObserver.CreateInProgramData(edtFilename.Text, X2LogLevelsDefault, True, textFormatter)
else if rbUserData.Checked then
- FFileObserver := TX2LogFileObserver.CreateInUserAppData(edtFilename.Text)
+ FFileObserver := TX2LogFileObserver.CreateInUserAppData(edtFilename.Text, X2LogLevelsDefault, True, textFormatter)
else
- FFileObserver := TX2LogFileObserver.Create(edtFilename.Text);
+ FFileObserver := TX2LogFileObserver.Create(edtFilename.Text, X2LogLevelsDefault, True, textFormatter);
FLog.Attach(FFileObserver);
@@ -371,18 +386,23 @@ end;
procedure TMainForm.btnRollingFileStartClick(Sender: TObject);
var
days: Integer;
+ textFormatter: IX2LogTextFormatter;
begin
if not Assigned(FRollingFileObserver) then
begin
+ textFormatter := nil;
+ if rbFileTextFormatterJson.Checked then
+ textFormatter := TX2LogJsonTextFormatter.Create;
+
days := StrToIntDef(edtRollingDays.Text, 7);
if rbRollingProgramData.Checked then
- FRollingFileObserver := TX2RollingLogFileObserver.CreateInProgramData(edtFilename.Text, days)
+ FRollingFileObserver := TX2RollingLogFileObserver.CreateInProgramData(edtFilename.Text, days, X2LogLevelsDefault, True, textFormatter)
else if rbRollingUserData.Checked then
- FRollingFileObserver := TX2RollingLogFileObserver.CreateInUserAppData(edtFilename.Text, days)
+ FRollingFileObserver := TX2RollingLogFileObserver.CreateInUserAppData(edtFilename.Text, days, X2LogLevelsDefault, True, textFormatter)
else
- FRollingFileObserver := TX2RollingLogFileObserver.Create(edtFilename.Text, days);
+ FRollingFileObserver := TX2RollingLogFileObserver.Create(edtFilename.Text, days, X2LogLevelsDefault, True, textFormatter);
FLog.Attach(FRollingFileObserver);
diff --git a/X2Log.Constants.pas b/X2Log.Constants.pas
index 0e64794..8a25ffe 100644
--- a/X2Log.Constants.pas
+++ b/X2Log.Constants.pas
@@ -48,6 +48,12 @@ resourcestring
}
LogFileLineDetails = '%0:s (details: %1:s)';
+ { The format of the log message when structured information is present
+
+ 0: Message
+ 1: Comma-separated key-value pairs
+ }
+ LogFileLineStructured = '%0:s [%1:s]';
{
X2Log.Observer.RollingLogFile
diff --git a/X2Log.Details.Default.pas b/X2Log.Details.Default.pas
index 9720ebf..8b084f0 100644
--- a/X2Log.Details.Default.pas
+++ b/X2Log.Details.Default.pas
@@ -45,6 +45,9 @@ type
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); virtual; abstract;
procedure SaveToStream(AStream: TStream); virtual; abstract;
+ function GetDisplayValue: string; virtual; abstract;
+
+ property DisplayValue: string read GetDisplayValue;
property ValueType: TX2LogValueType read FValueType;
end;
@@ -80,6 +83,7 @@ type
{ IX2LogDetailsDictionary }
function GetKeys: TEnumerable;
function GetValueType(const Key: string): TX2LogValueType;
+ function GetDisplayValue(const Key: string): string;
function GetStringValue(const Key: string): string;
function GetBooleanValue(const Key: string): Boolean;
@@ -201,6 +205,8 @@ type
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); override;
procedure SaveToStream(AStream: TStream); override;
+ function GetDisplayValue: string; override;
+
property Value: string read FValue write FValue;
end;
@@ -214,6 +220,8 @@ type
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); override;
procedure SaveToStream(AStream: TStream); override;
+ function GetDisplayValue: string; override;
+
property Value: Boolean read FValue write FValue;
end;
@@ -227,6 +235,8 @@ type
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); override;
procedure SaveToStream(AStream: TStream); override;
+ function GetDisplayValue: string; override;
+
property Value: Int64 read FValue write FValue;
end;
@@ -240,6 +250,8 @@ type
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); override;
procedure SaveToStream(AStream: TStream); override;
+ function GetDisplayValue: string; override;
+
property Value: Extended read FValue write FValue;
end;
@@ -253,6 +265,8 @@ type
procedure LoadFromStream(AStream: TStream; ASize: Cardinal); override;
procedure SaveToStream(AStream: TStream); override;
+ function GetDisplayValue: string; override;
+
property Value: TDateTime read FValue write FValue;
end;
@@ -424,6 +438,12 @@ begin
end;
+function TX2LogDictionaryDetails.GetDisplayValue(const Key: string): string;
+begin
+ Result := FValues[Key].DisplayValue;
+end;
+
+
function TX2LogDictionaryDetails.GetStringValue(const Key: string): string;
begin
Result := (FValues[Key] as TX2LogDictionaryStringValue).Value;
@@ -768,6 +788,12 @@ begin
end;
+function TX2LogDictionaryStringValue.GetDisplayValue: string;
+begin
+ Result := Value;
+end;
+
+
{ TX2LogDictionaryBooleanValue }
constructor TX2LogDictionaryBooleanValue.Create(AValue: Boolean);
begin
@@ -792,6 +818,12 @@ begin
end;
+function TX2LogDictionaryBooleanValue.GetDisplayValue: string;
+begin
+ Result := BoolToStr(Value, True);
+end;
+
+
{ TX2LogDictionaryIntValue }
constructor TX2LogDictionaryIntValue.Create(AValue: Int64);
begin
@@ -816,6 +848,12 @@ begin
end;
+function TX2LogDictionaryIntValue.GetDisplayValue: string;
+begin
+ Result := IntToStr(Value);
+end;
+
+
{ TX2LogDictionaryFloatValue }
constructor TX2LogDictionaryFloatValue.Create(AValue: Extended);
begin
@@ -840,6 +878,12 @@ begin
end;
+function TX2LogDictionaryFloatValue.GetDisplayValue: string;
+begin
+ Result := FormatFloat('0.########', Value);
+end;
+
+
{ TX2LogDictionaryDateTimeValue }
constructor TX2LogDictionaryDateTimeValue.Create(AValue: TDateTime);
begin
@@ -864,6 +908,12 @@ begin
end;
+function TX2LogDictionaryDateTimeValue.GetDisplayValue: string;
+begin
+ Result := DateTimeToStr(Value);
+end;
+
+
initialization
TX2LogDetailsRegistry.Register(StringDetailsSerializerIID, TX2LogStringDetailsSerializer.Create);
TX2LogDetailsRegistry.Register(DictionaryDetailsSerializerIID, TX2LogDictionaryDetailsSerializer.Create);
diff --git a/X2Log.Details.Intf.pas b/X2Log.Details.Intf.pas
index dc0f59f..8a87a5c 100644
--- a/X2Log.Details.Intf.pas
+++ b/X2Log.Details.Intf.pas
@@ -25,6 +25,7 @@ type
function GetKeys: TEnumerable;
function GetValueType(const Key: string): TX2LogValueType;
+ function GetDisplayValue(const Key: string): string;
function GetStringValue(const Key: string): string;
function GetBooleanValue(const Key: string): Boolean;
function GetIntValue(const Key: string): Int64;
@@ -33,6 +34,7 @@ type
property Keys: TEnumerable read GetKeys;
property ValueType[const Key: string]: TX2LogValueType read GetValueType;
+ property DisplayValue[const Key: string]: string read GetDisplayValue;
property StringValue[const Key: string]: string read GetStringValue;
property BooleanValue[const Key: string]: Boolean read GetBooleanValue;
diff --git a/X2Log.Observer.LogFile.pas b/X2Log.Observer.LogFile.pas
index 5f63c05..ff9faeb 100644
--- a/X2Log.Observer.LogFile.pas
+++ b/X2Log.Observer.LogFile.pas
@@ -8,7 +8,8 @@ uses
X2Log.Intf,
X2Log.Observer.Custom,
- X2Log.Observer.CustomThreaded;
+ X2Log.Observer.CustomThreaded,
+ X2Log.TextFormatter.Intf;
type
@@ -16,15 +17,17 @@ type
private
FOutputFileName: string;
FLogDetails: Boolean;
+ FTextFormatter: IX2LogTextFormatter;
protected
+ function GetTextFormatter: IX2LogTextFormatter; virtual;
function CreateWorkerThread: TX2LogObserverWorkerThread; override;
property OutputFileName: string read FOutputFileName;
property LogDetails: Boolean read FLogDetails;
public
- constructor Create(const AOutputFileName: string; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True);
- constructor CreateInProgramData(const AOutputFileName: string; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True);
- constructor CreateInUserAppData(const AOutputFileName: string; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True);
+ constructor Create(const AOutputFileName: string; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True; ATextFormatter: IX2LogTextFormatter = nil);
+ constructor CreateInProgramData(const AOutputFileName: string; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True; ATextFormatter: IX2LogTextFormatter = nil);
+ constructor CreateInUserAppData(const AOutputFileName: string; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True; ATextFormatter: IX2LogTextFormatter = nil);
end;
@@ -32,14 +35,16 @@ type
private
FOutputFileName: string;
FLogDetails: Boolean;
+ FTextFormatter: IX2LogTextFormatter;
protected
function GetFileName(AEntry: TX2LogQueueEntry): string; virtual;
procedure ProcessEntry(AEntry: TX2LogQueueEntry); override;
property OutputFileName: string read FOutputFileName;
property LogDetails: Boolean read FLogDetails;
+ property TextFormatter: IX2LogTextFormatter read FTextFormatter;
public
- constructor Create(const AOutputFileName: string; ALogDetails: Boolean = True);
+ constructor Create(const AOutputFileName: string; ATextFormatter: IX2LogTextFormatter; ALogDetails: Boolean = True);
end;
@@ -51,21 +56,42 @@ uses
Winapi.SHFolder,
Winapi.Windows,
- X2Log.Constants;
+ X2Log.Constants,
+ X2Log.TextFormatter.Default;
+type
+ TX2LogFileTextFormatterHelper = class(TInterfacedObject, IX2LogTextFormatterHelper)
+ private
+ FEntry: TX2LogQueueEntry;
+ FLogFileName: string;
+ FLogDetails: Boolean;
+ protected
+ { IX2LogTextFormatterHelper }
+ function GetDetailsFilename: string;
+
+ property Entry: TX2LogQueueEntry read FEntry;
+ property LogFileName: string read FLogFileName;
+ property LogDetails: Boolean read FLogDetails;
+ public
+ constructor Create(AEntry: TX2LogQueueEntry; const ALogFileName: string; ALogDetails: Boolean);
+ end;
+
{ TX2LogFileObserver }
-constructor TX2LogFileObserver.Create(const AOutputFileName: string; ALogLevels: TX2LogLevels; ALogDetails: Boolean);
+constructor TX2LogFileObserver.Create(const AOutputFileName: string; ALogLevels: TX2LogLevels; ALogDetails: Boolean; ATextFormatter: IX2LogTextFormatter);
begin
FOutputFileName := AOutputFileName;
FLogDetails := ALogDetails;
+ FTextFormatter := ATextFormatter;
+ if not Assigned(FTextFormatter) then
+ FTextFormatter := TX2LogDefaultTextFormatter.Create;
inherited Create(ALogLevels);
end;
-constructor TX2LogFileObserver.CreateInProgramData(const AOutputFileName: string; ALogLevels: TX2LogLevels; ALogDetails: Boolean);
+constructor TX2LogFileObserver.CreateInProgramData(const AOutputFileName: string; ALogLevels: TX2LogLevels; ALogDetails: Boolean; ATextFormatter: IX2LogTextFormatter);
var
path: PWideChar;
@@ -73,14 +99,14 @@ begin
GetMem(path, MAX_PATH);
try
OleCheck(SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, SHGFP_TYPE_CURRENT, path));
- Create(IncludeTrailingPathDelimiter(path) + AOutputFileName, ALogLevels, ALogDetails);
+ Create(IncludeTrailingPathDelimiter(path) + AOutputFileName, ALogLevels, ALogDetails, ATextFormatter);
finally
FreeMem(path);
end;
end;
-constructor TX2LogFileObserver.CreateInUserAppData(const AOutputFileName: string; ALogLevels: TX2LogLevels; ALogDetails: Boolean);
+constructor TX2LogFileObserver.CreateInUserAppData(const AOutputFileName: string; ALogLevels: TX2LogLevels; ALogDetails: Boolean; ATextFormatter: IX2LogTextFormatter);
var
path: PWideChar;
@@ -88,7 +114,7 @@ begin
GetMem(path, MAX_PATH);
try
OleCheck(SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, path));
- Create(IncludeTrailingPathDelimiter(path) + AOutputFileName, ALogLevels, ALogDetails);
+ Create(IncludeTrailingPathDelimiter(path) + AOutputFileName, ALogLevels, ALogDetails, ATextFormatter);
finally
FreeMem(path);
end;
@@ -97,15 +123,22 @@ end;
function TX2LogFileObserver.CreateWorkerThread: TX2LogObserverWorkerThread;
begin
- Result := TX2LogFileWorkerThread.Create(OutputFileName, LogDetails);
+ Result := TX2LogFileWorkerThread.Create(OutputFileName, GetTextFormatter, LogDetails);
+end;
+
+
+function TX2LogFileObserver.GetTextFormatter: IX2LogTextFormatter;
+begin
+ Result := FTextFormatter;
end;
{ TX2LogFileWorkerThread }
-constructor TX2LogFileWorkerThread.Create(const AOutputFileName: string; ALogDetails: Boolean);
+constructor TX2LogFileWorkerThread.Create(const AOutputFileName: string; ATextFormatter: IX2LogTextFormatter; ALogDetails: Boolean);
begin
FOutputFileName := AOutputFileName;
FLogDetails := ALogDetails;
+ FTextFormatter := ATextFormatter;
inherited Create;
end;
@@ -114,29 +147,62 @@ end;
procedure TX2LogFileWorkerThread.ProcessEntry(AEntry: TX2LogQueueEntry);
var
fileName: string;
- baseReportFileName: string;
- errorMsg: string;
- detailsExtension: string;
- detailsFile: THandle;
- detailsFileStream: THandleStream;
- detailsFileName: string;
- detailsNumber: Integer;
+ line: string;
writer: TStreamWriter;
- logDetailsStreamable: IX2LogDetailsStreamable;
begin
fileName := GetFileName(AEntry);
ForceDirectories(ExtractFilePath(fileName));
- if Length(AEntry.Category) > 0 then
- errorMsg := Format(GetLogResourceString(@LogFileLineCategory), [AEntry.Message, AEntry.Category])
- else
- errorMsg := Format(GetLogResourceString(@LogFileLineNoCategory), [AEntry.Message]);
+ line := TextFormatter.GetText(TX2LogFileTextFormatterHelper.Create(AEntry, fileName, LogDetails),
+ AEntry.Level, AEntry.DateTime, AEntry.Message, AEntry.Category, AEntry.Details);
- if LogDetails and Supports(AEntry.Details, IX2LogDetailsStreamable, logDetailsStreamable) then
+ { Append line to log file }
+ writer := TFile.AppendText(fileName);
+ try
+ writer.WriteLine(line);
+ finally
+ FreeAndNil(writer);
+ end;
+end;
+
+
+function TX2LogFileWorkerThread.GetFileName(AEntry: TX2LogQueueEntry): string;
+begin
+ Result := FOutputFileName;
+end;
+
+
+{ TX2LogFileTextFormatterHelper }
+constructor TX2LogFileTextFormatterHelper.Create(AEntry: TX2LogQueueEntry; const ALogFileName: string; ALogDetails: Boolean);
+begin
+ inherited Create;
+
+ FEntry := AEntry;
+ FLogFileName := ALogFileName;
+ FLogDetails := ALogDetails;
+end;
+
+
+function TX2LogFileTextFormatterHelper.GetDetailsFilename: string;
+var
+ logDetailsStreamable: IX2LogDetailsStreamable;
+ baseReportFileName: string;
+ detailsExtension: string;
+ detailsFile: THandle;
+ detailsFileStream: THandleStream;
+ detailsFileName: string;
+ detailsNumber: Integer;
+
+begin
+ Result := '';
+ if not LogDetails then
+ exit;
+
+ if Supports(Entry.Details, IX2LogDetailsStreamable, logDetailsStreamable) then
begin
- detailsExtension := ExtractFileExt(fileName);
- baseReportFileName := ChangeFileExt(fileName, '_' + FormatDateTime(GetLogResourceString(@LogFileNameDateFormat), AEntry.DateTime));
+ detailsExtension := ExtractFileExt(LogFileName);
+ baseReportFileName := ChangeFileExt(LogFileName, '_' + FormatDateTime(GetLogResourceString(@LogFileNameDateFormat), Entry.DateTime));
detailsFileName := baseReportFileName + detailsExtension;
detailsNumber := 0;
@@ -172,29 +238,12 @@ begin
CloseHandle(detailsFile);
end;
- // ErrorLogs.Add(reportFileName);
-
- errorMsg := Format(GetLogResourceString(@LogFileLineDetails), [errorMsg, ExtractFileName(detailsFileName)]);
+ Result := detailsFileName;
break;
end;
until False;
end;
end;
-
- { Append line to log file }
- writer := TFile.AppendText(fileName);
- try
- writer.WriteLine('[' + FormatDateTime(GetLogResourceString(@LogFileLineDateFormat), AEntry.DateTime) + '] ' +
- GetLogLevelText(AEntry.Level) + ': ' + errorMsg);
- finally
- FreeAndNil(writer);
- end;
-end;
-
-
-function TX2LogFileWorkerThread.GetFileName(AEntry: TX2LogQueueEntry): string;
-begin
- Result := FOutputFileName;
end;
end.
diff --git a/X2Log.Observer.MonitorForm.dfm b/X2Log.Observer.MonitorForm.dfm
index 3a51ad2..165ddff 100644
--- a/X2Log.Observer.MonitorForm.dfm
+++ b/X2Log.Observer.MonitorForm.dfm
@@ -284,7 +284,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
Left = 448
Top = 48
Bitmap = {
- 494C01010A004000000110001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
+ 494C01010A004000040110001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/X2Log.Observer.MonitorForm.pas b/X2Log.Observer.MonitorForm.pas
index 0ee67ec..8e475c2 100644
--- a/X2Log.Observer.MonitorForm.pas
+++ b/X2Log.Observer.MonitorForm.pas
@@ -745,25 +745,12 @@ end;
procedure TX2LogObserverMonitorForm.SetDictionaryDetails(ADetails: IX2LogDetailsDictionary);
var
key: string;
- displayValue: string;
begin
vleDetailsDictionary.Strings.Clear;
for key in ADetails.Keys do
- begin
- displayValue := '';
-
- case ADetails.ValueType[key] of
- StringValue: displayValue := ADetails.StringValue[key];
- BooleanValue: displayValue := BoolToStr(ADetails.BooleanValue[key], True);
- IntValue: displayValue := IntToStr(ADetails.IntValue[key]);
- FloatValue: displayValue := FormatFloat('0.########', ADetails.FloatValue[key]);
- DateTimeValue: displayValue := DateTimeToStr(ADetails.DateTimeValue[key]);
- end;
-
- vleDetailsDictionary.Values[key] := displayValue;
- end;
+ vleDetailsDictionary.Values[key] := ADetails.DisplayValue[key];
FCopyHandler := CopyDictionaryDetails;
SetVisibleDetails(vleDetailsDictionary);
diff --git a/X2Log.Observer.RollingLogFile.pas b/X2Log.Observer.RollingLogFile.pas
index 3958202..c4ccd77 100644
--- a/X2Log.Observer.RollingLogFile.pas
+++ b/X2Log.Observer.RollingLogFile.pas
@@ -6,7 +6,8 @@ uses
X2Log.Intf,
X2Log.Observer.CustomThreaded,
- X2Log.Observer.LogFile;
+ X2Log.Observer.LogFile,
+ X2Log.TextFormatter.Intf;
const
@@ -22,9 +23,9 @@ type
property Days: Integer read FDays;
public
- constructor Create(const AFileName: string; ADays: Integer = X2LogDefaultDays; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True);
- constructor CreateInProgramData(const AFileName: string; ADays: Integer = X2LogDefaultDays; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True);
- constructor CreateInUserAppData(const AFileName: string; ADays: Integer = X2LogDefaultDays; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True);
+ constructor Create(const AFileName: string; ADays: Integer = X2LogDefaultDays; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True; ATextFormatter: IX2LogTextFormatter = nil);
+ constructor CreateInProgramData(const AFileName: string; ADays: Integer = X2LogDefaultDays; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True; ATextFormatter: IX2LogTextFormatter = nil);
+ constructor CreateInUserAppData(const AFileName: string; ADays: Integer = X2LogDefaultDays; ALogLevels: TX2LogLevels = X2LogLevelsDefault; ALogDetails: Boolean = True; ATextFormatter: IX2LogTextFormatter = nil);
end;
@@ -44,7 +45,7 @@ type
property Days: Integer read FDays;
property LastCleanupDate: TDateTime read FLastCleanupDate write FLastCleanupDate;
public
- constructor Create(const AFileName: string; ADays: Integer; ALogDetails: Boolean = True);
+ constructor Create(const AFileName: string; ADays: Integer; ATextFormatter: IX2LogTextFormatter; ALogDetails: Boolean = True);
end;
@@ -59,44 +60,44 @@ uses
{ TX2RollingLogFileObserver }
-constructor TX2RollingLogFileObserver.Create(const AFileName: string; ADays: Integer; ALogLevels: TX2LogLevels; ALogDetails: Boolean);
+constructor TX2RollingLogFileObserver.Create(const AFileName: string; ADays: Integer; ALogLevels: TX2LogLevels; ALogDetails: Boolean; ATextFormatter: IX2LogTextFormatter);
begin
FDays := ADays;
- inherited Create(AFileName, ALogLevels, ALogDetails);
+ inherited Create(AFileName, ALogLevels, ALogDetails, ATextFormatter);
end;
-constructor TX2RollingLogFileObserver.CreateInProgramData(const AFileName: string; ADays: Integer; ALogLevels: TX2LogLevels; ALogDetails: Boolean);
+constructor TX2RollingLogFileObserver.CreateInProgramData(const AFileName: string; ADays: Integer; ALogLevels: TX2LogLevels; ALogDetails: Boolean; ATextFormatter: IX2LogTextFormatter);
begin
FDays := ADays;
- inherited CreateInProgramData(AFileName, ALogLevels, ALogDetails);
+ inherited CreateInProgramData(AFileName, ALogLevels, ALogDetails, ATextFormatter);
end;
-constructor TX2RollingLogFileObserver.CreateInUserAppData(const AFileName: string; ADays: Integer; ALogLevels: TX2LogLevels; ALogDetails: Boolean);
+constructor TX2RollingLogFileObserver.CreateInUserAppData(const AFileName: string; ADays: Integer; ALogLevels: TX2LogLevels; ALogDetails: Boolean; ATextFormatter: IX2LogTextFormatter);
begin
FDays := ADays;
- inherited CreateInUserAppData(AFileName, ALogLevels, ALogDetails);
+ inherited CreateInUserAppData(AFileName, ALogLevels, ALogDetails, ATextFormatter);
end;
function TX2RollingLogFileObserver.CreateWorkerThread: TX2LogObserverWorkerThread;
begin
- Result := TX2RollingLogFileWorkerThread.Create(OutputFileName, Days, LogDetails);
+ Result := TX2RollingLogFileWorkerThread.Create(OutputFileName, Days, GetTextFormatter, LogDetails);
end;
{ TX2RollingLogFileWorkerThread }
-constructor TX2RollingLogFileWorkerThread.Create(const AFileName: string; ADays: Integer; ALogDetails: Boolean);
+constructor TX2RollingLogFileWorkerThread.Create(const AFileName: string; ADays: Integer; ATextFormatter: IX2LogTextFormatter; ALogDetails: Boolean);
begin
FDays := ADays;
FFormatSettings := TFormatSettings.Create;
FDateFormat := GetLogResourceString(@RollingLogFileDateFormat);
- inherited Create(AFileName, ALogDetails);
+ inherited Create(AFileName, ATextFormatter, ALogDetails);
end;
diff --git a/X2Log.TextFormatter.Default.pas b/X2Log.TextFormatter.Default.pas
new file mode 100644
index 0000000..9cb1341
--- /dev/null
+++ b/X2Log.TextFormatter.Default.pas
@@ -0,0 +1,78 @@
+unit X2Log.TextFormatter.Default;
+
+interface
+uses
+ X2Log.Details.Intf,
+ X2Log.Intf,
+ X2Log.TextFormatter.Intf;
+
+
+type
+ TX2LogDefaultTextFormatter = class(TInterfacedObject, IX2LogTextFormatter)
+ protected
+ function GetText(AHelper: IX2LogTextFormatterHelper; ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; const ACategory: string; ADetails: IX2LogDetails): string;
+
+ function GetDictionaryDisplayValues(ADetails: IX2LogDetailsDictionary): string;
+ end;
+
+
+implementation
+uses
+ System.SysUtils,
+
+ X2Log.Constants;
+
+
+{ TX2LogDefaultTextFormatter }
+function TX2LogDefaultTextFormatter.GetText(AHelper: IX2LogTextFormatterHelper; ALevel: TX2LogLevel; ADateTime: TDateTime;
+ const AMessage, ACategory: string; ADetails: IX2LogDetails): string;
+var
+ line: string;
+ dictionaryDetails: IX2LogDetailsDictionary;
+ detailsFileName: string;
+
+begin
+ if Length(ACategory) > 0 then
+ line := Format(GetLogResourceString(@LogFileLineCategory), [AMessage, ACategory])
+ else
+ line := Format(GetLogResourceString(@LogFileLineNoCategory), [AMessage]);
+
+ if Supports(ADetails, IX2LogDetailsDictionary, dictionaryDetails) then
+ begin
+ line := Format(GetLogResourceString(@LogFileLineStructured), [line, GetDictionaryDisplayValues(dictionaryDetails)]);
+ end else
+ begin
+ detailsFileName := AHelper.GetDetailsFilename;
+ if Length(detailsFileName) > 0 then
+ line := Format(GetLogResourceString(@LogFileLineDetails), [line, ExtractFileName(detailsFileName)]);
+ end;
+
+ Result := '[' + FormatDateTime(GetLogResourceString(@LogFileLineDateFormat), ADateTime) + '] ' +
+ GetLogLevelText(ALevel) + ': ' + line;
+
+end;
+
+
+function TX2LogDefaultTextFormatter.GetDictionaryDisplayValues(ADetails: IX2LogDetailsDictionary): string;
+var
+ displayValues: TStringBuilder;
+ key: string;
+
+begin
+ displayValues := TStringBuilder.Create;
+ try
+ for key in ADetails.Keys do
+ begin
+ if displayValues.Length > 0 then
+ displayValues.Append(', ');
+
+ displayValues.Append(key).Append(': ').Append(ADetails.DisplayValue[key]);
+ end;
+
+ Result := displayValues.ToString;
+ finally
+ FreeAndNil(displayValues);
+ end;
+end;
+
+end.
diff --git a/X2Log.TextFormatter.Intf.pas b/X2Log.TextFormatter.Intf.pas
new file mode 100644
index 0000000..fd7acfd
--- /dev/null
+++ b/X2Log.TextFormatter.Intf.pas
@@ -0,0 +1,20 @@
+unit X2Log.TextFormatter.Intf;
+
+interface
+uses
+ X2Log.Intf;
+
+type
+ IX2LogTextFormatterHelper = interface
+ ['{D1A1DAD5-0F96-491F-8BD5-0B9D0BE87C32}']
+ function GetDetailsFilename: string;
+ end;
+
+ IX2LogTextFormatter = interface
+ ['{C49BE49D-8563-4097-A2B7-0869F27F5EDD}']
+ function GetText(AHelper: IX2LogTextFormatterHelper; ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; const ACategory: string; ADetails: IX2LogDetails): string;
+ end;
+
+implementation
+
+end.
diff --git a/X2Log.TextFormatter.Json.pas b/X2Log.TextFormatter.Json.pas
new file mode 100644
index 0000000..c71d8ab
--- /dev/null
+++ b/X2Log.TextFormatter.Json.pas
@@ -0,0 +1,127 @@
+unit X2Log.TextFormatter.Json;
+
+interface
+uses
+ JsonDataObjects,
+
+ X2Log.Details.Intf,
+ X2Log.Intf,
+ X2Log.TextFormatter.Intf;
+
+type
+ {
+ Default:
+ X2Log naming convention is used for the property names
+
+ Kibana:
+ Logstash/Kibana compatible (message, @timestamp, @version) property names
+ }
+ TX2LogJsonStyle = (Default, Kibana);
+
+
+ TX2LogJsonTextFormatter = class(TInterfacedObject, IX2LogTextFormatter)
+ private
+ FSingleLine: Boolean;
+ FStyle: TX2LogJsonStyle;
+ protected
+ function GetText(AHelper: IX2LogTextFormatterHelper; ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; const ACategory: string; ADetails: IX2LogDetails): string;
+ procedure AddDictionaryDetails(AObject: TJsonObject; ADetails: IX2LogDetailsDictionary);
+
+ property SingleLine: Boolean read FSingleLine;
+ property Style: TX2LogJsonStyle read FStyle;
+ public
+ constructor Create(ASingleLine: Boolean = True; AStyle: TX2LogJsonStyle = TX2LogJsonStyle.Default);
+ end;
+
+
+implementation
+uses
+ System.SysUtils;
+
+
+{ TX2LogJsonTextFormatter }
+constructor TX2LogJsonTextFormatter.Create(ASingleLine: Boolean; AStyle: TX2LogJsonStyle);
+begin
+ inherited Create;
+
+ FSingleLine := ASingleLine;
+ FStyle := AStyle;
+end;
+
+
+function TX2LogJsonTextFormatter.GetText(AHelper: IX2LogTextFormatterHelper; ALevel: TX2LogLevel; ADateTime: TDateTime;
+ const AMessage, ACategory: string; ADetails: IX2LogDetails): string;
+const
+ LogLevelName: array[TX2LogLevel] of string = ('verbose', 'info', 'warning', 'error');
+
+var
+ line: TJsonObject;
+ dictionaryDetails: IX2LogDetailsDictionary;
+ detailsFileName: string;
+
+begin
+ line := TJsonObject.Create;
+ try
+ case Style of
+ Default:
+ begin
+ line.D['dateTime'] := ADateTime;
+ line.S['message'] := AMessage;
+ end;
+
+ Kibana:
+ begin
+ line.S['@version'] := '1';
+ line.D['@timestamp'] := ADateTime;
+ line.S['message'] := AMessage;
+ end;
+ end;
+
+ line.S['level'] := LogLevelName[ALevel];
+ line.S['category'] := ACategory;
+
+ if Supports(ADetails, IX2LogDetailsDictionary, dictionaryDetails) then
+ begin
+ AddDictionaryDetails(line, dictionaryDetails);
+ end else
+ begin
+ detailsFileName := AHelper.GetDetailsFilename;
+ if Length(detailsFileName) > 0 then
+ line.S['details'] := ExtractFileName(detailsFileName);
+ end;
+
+ Result := line.ToJSON(SingleLine);
+ finally
+ FreeAndNil(line);
+ end;
+end;
+
+
+procedure TX2LogJsonTextFormatter.AddDictionaryDetails(AObject: TJsonObject; ADetails: IX2LogDetailsDictionary);
+var
+ key: string;
+ jsonKey: string;
+ valueType: TX2LogValueType;
+
+begin
+ for key in ADetails.Keys do
+ begin
+ jsonKey := key;
+ while AObject.Contains(jsonKey) do
+ jsonKey := '_' + jsonKey;
+
+ valueType := ADetails.ValueType[key];
+
+ case valueType of
+ StringValue: AObject.S[jsonKey] := ADetails.StringValue[key];
+ BooleanValue: AObject.B[jsonKey] := ADetails.BooleanValue[key];
+ IntValue: AObject.L[jsonKey] := ADetails.IntValue[key];
+ FloatValue: AObject.F[jsonKey] := ADetails.FloatValue[key];
+ DateTimeValue: AObject.D[jsonKey] := ADetails.DateTimeValue[key];
+ else
+ AObject.S[jsonKey] := Format('', [Ord(valueType)]);
+ end;
+ end;
+end;
+
+end.