diff --git a/Packages/DXE2/X2LogJsonDXE2.dpk b/Packages/DXE2/X2LogJsonDXE2.dpk index 231d32c..aa92f19 100644 --- a/Packages/DXE2/X2LogJsonDXE2.dpk +++ b/Packages/DXE2/X2LogJsonDXE2.dpk @@ -30,7 +30,8 @@ package X2LogJsonDXE2; requires rtl, - X2LogDXE2; + X2LogDXE2, + soaprtl; contains X2Log.TextFormatter.Json in '..\..\X2Log.TextFormatter.Json.pas', diff --git a/Packages/DXE2/X2LogJsonDXE2.dproj b/Packages/DXE2/X2LogJsonDXE2.dproj index 63fae2e..837cd8d 100644 --- a/Packages/DXE2/X2LogJsonDXE2.dproj +++ b/Packages/DXE2/X2LogJsonDXE2.dproj @@ -125,6 +125,7 @@ + diff --git a/X2Log.Observer.LogFile.pas b/X2Log.Observer.LogFile.pas index ff9faeb..4187c32 100644 --- a/X2Log.Observer.LogFile.pas +++ b/X2Log.Observer.LogFile.pas @@ -69,6 +69,7 @@ type protected { IX2LogTextFormatterHelper } function GetDetailsFilename: string; + function SaveDetailsToStream(AStream: TStream): Boolean; property Entry: TX2LogQueueEntry read FEntry; property LogFileName: string read FLogFileName; @@ -246,4 +247,15 @@ begin end; end; + +function TX2LogFileTextFormatterHelper.SaveDetailsToStream(AStream: TStream): Boolean; +var + logDetailsStreamable: IX2LogDetailsStreamable; + +begin + Result := Supports(Entry.Details, IX2LogDetailsStreamable, logDetailsStreamable); + if Result then + logDetailsStreamable.SaveToStream(AStream); +end; + end. diff --git a/X2Log.TextFormatter.Intf.pas b/X2Log.TextFormatter.Intf.pas index 020c275..5c1db64 100644 --- a/X2Log.TextFormatter.Intf.pas +++ b/X2Log.TextFormatter.Intf.pas @@ -2,12 +2,15 @@ unit X2Log.TextFormatter.Intf; interface uses + System.Classes, + X2Log.Intf; type IX2LogTextFormatterHelper = interface ['{D1A1DAD5-0F96-491F-8BD5-0B9D0BE87C32}'] function GetDetailsFilename: string; + function SaveDetailsToStream(AStream: TStream): Boolean; end; IX2LogTextFormatter = interface diff --git a/X2Log.TextFormatter.Json.pas b/X2Log.TextFormatter.Json.pas index df71314..61f192a 100644 --- a/X2Log.TextFormatter.Json.pas +++ b/X2Log.TextFormatter.Json.pas @@ -22,29 +22,45 @@ type TX2LogJsonTextFormatter = class(TInterfacedObject, IX2LogTextFormatter) private FSingleLine: Boolean; + FInlineFields: Boolean; + FInlineDetails: 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 InlineFields: Boolean read FInlineFields; + property InlineDetails: Boolean read FInlineDetails; property Style: TX2LogJsonStyle read FStyle; public - constructor Create(ASingleLine: Boolean = True; AStyle: TX2LogJsonStyle = TX2LogJsonStyle.Default); + { + If AInlineFields is False, dictionary keys/values will be added to a + "fields" object instead of directly in the message, similar to Serilog's + Elasticsearch sink option with the same name. + + If AInlineDetails is False, the details will be written to a separate file and + the detailsFile value will contain the file name. + } + constructor Create(ASingleLine: Boolean = True; AStyle: TX2LogJsonStyle = TX2LogJsonStyle.Default; AInlineFields: Boolean = True; AInlineDetails: Boolean = True); end; implementation uses + Soap.EncdDecd, + System.Classes, System.SysUtils; { TX2LogJsonTextFormatter } -constructor TX2LogJsonTextFormatter.Create(ASingleLine: Boolean; AStyle: TX2LogJsonStyle); +constructor TX2LogJsonTextFormatter.Create(ASingleLine: Boolean; AStyle: TX2LogJsonStyle; AInlineFields, AInlineDetails: Boolean); begin inherited Create; FSingleLine := ASingleLine; + FInlineFields := AInlineFields; + FInlineDetails := AInlineDetails; FStyle := AStyle; end; @@ -58,6 +74,8 @@ var line: TJsonObject; dictionaryDetails: IX2LogDetailsDictionary; detailsFileName: string; + detailsStream: TMemoryStream; + encodedStream: TStringStream; begin line := TJsonObject.Create; @@ -82,12 +100,36 @@ begin if Supports(ADetails, IX2LogDetailsDictionary, dictionaryDetails) then begin - AddDictionaryDetails(line, dictionaryDetails); + if InlineFields then + AddDictionaryDetails(line, dictionaryDetails) + else + AddDictionaryDetails(line.O['fields'], dictionaryDetails); end else begin - detailsFileName := AHelper.GetDetailsFilename; - if Length(detailsFileName) > 0 then - line.S['details'] := ExtractFileName(detailsFileName); + if InlineDetails then + begin + detailsStream := TMemoryStream.Create; + try + if AHelper.SaveDetailsToStream(detailsStream) then + begin + detailsStream.Position := 0; + encodedStream := TStringStream.Create; + try + EncodeStream(detailsStream, encodedStream); + line.S['details'] := encodedStream.DataString; + finally + FreeAndNil(encodedStream); + end; + end; + finally + FreeAndNil(detailsStream); + end; + end else + begin + detailsFileName := AHelper.GetDetailsFilename; + if Length(detailsFileName) > 0 then + line.S['detailsFile'] := ExtractFileName(detailsFileName); + end; end; Result := line.ToJSON(SingleLine);