Skip to content
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
84 changes: 59 additions & 25 deletions src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -105,6 +106,10 @@ JsonEncodedText LineNumber

public delegate void JsonLogInfoFormatter(Utf8JsonWriter jsonWriter, in LogInfo info);

public delegate void JsonLogEntryFormatter(Utf8JsonWriter jsonWriter, IZLoggerEntry entry);

public delegate void JsonExceptionFormatter(Utf8JsonWriter jsonWriter, Exception exception);

public class SystemTextJsonZLoggerFormatter : IZLoggerFormatter
{
public JsonPropertyNames JsonPropertyNames { get; set; } = JsonPropertyNames.Default;
Expand All @@ -121,6 +126,8 @@ public class SystemTextJsonZLoggerFormatter : IZLoggerFormatter
};

public JsonLogInfoFormatter? AdditionalFormatter { get; set; }
public JsonExceptionFormatter? ExceptionFormatter { get; set; }
public JsonLogEntryFormatter? PropertyKeyValuesFormatter { get; set; }
public JsonEncodedText? PropertyKeyValuesObjectName { get; set; } // if null(default), non nested.
public IKeyNameMutator? KeyNameMutator { get; set; }
public bool UseUtcTimestamp { get; set; } // default is false, use Local.
Expand Down Expand Up @@ -187,22 +194,42 @@ public void FormatLogEntry(IBufferWriter<byte> writer, IZLoggerEntry entry)
// Params
if ((IncludeProperties & IncludeProperties.ParameterKeyValues) != 0)
{
if (PropertyKeyValuesObjectName == null)
{
entry.WriteJsonParameterKeyValues(jsonWriter, JsonSerializerOptions, KeyNameMutator);
}
else
{
jsonWriter.WriteStartObject(PropertyKeyValuesObjectName.Value);
entry.WriteJsonParameterKeyValues(jsonWriter, JsonSerializerOptions, KeyNameMutator);
jsonWriter.WriteEndObject();
}
WritePropertyKeyValues(jsonWriter, entry);
}

jsonWriter.WriteEndObject();
jsonWriter.Flush();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void WritePropertyKeyValues(Utf8JsonWriter jsonWriter, IZLoggerEntry entry)
{
if (PropertyKeyValuesObjectName == null)
{
if (PropertyKeyValuesFormatter is { } formatter)
{
formatter(jsonWriter, entry);
}
else
{
entry.WriteJsonParameterKeyValues(jsonWriter, JsonSerializerOptions, KeyNameMutator);
}
}
else
{
jsonWriter.WriteStartObject(PropertyKeyValuesObjectName.Value);
if (PropertyKeyValuesFormatter is { } formatter)
{
formatter(jsonWriter, entry);
}
else
{
entry.WriteJsonParameterKeyValues(jsonWriter, JsonSerializerOptions, KeyNameMutator);
}
jsonWriter.WriteEndObject();
}
}

JsonEncodedText LogLevelToEncodedText(LogLevel logLevel)
{
switch (logLevel)
Expand Down Expand Up @@ -252,9 +279,16 @@ void FormatLogInfo(Utf8JsonWriter jsonWriter, in LogInfo info)
if ((flag & IncludeProperties.Exception) != 0)
{
if (info.Exception is { } ex)
{
jsonWriter.WritePropertyName(JsonPropertyNames.Exception);
WriteException(jsonWriter, ex);
{
if (ExceptionFormatter is { } formatter)
{
formatter(jsonWriter, ex);
}
else
{
jsonWriter.WritePropertyName(JsonPropertyNames.Exception);
WriteException(jsonWriter, ex);
}
}
}
if ((flag & IncludeProperties.MemberName) != 0)
Expand All @@ -278,17 +312,17 @@ void WriteException(Utf8JsonWriter jsonWriter, Exception? ex)
jsonWriter.WriteNullValue();
}
else
{
jsonWriter.WriteStartObject();
{
jsonWriter.WriteString(JsonPropertyNames.ExceptionName, ex.GetType().FullName);
jsonWriter.WriteString(JsonPropertyNames.ExceptionMessage, ex.Message);
jsonWriter.WriteString(JsonPropertyNames.ExceptionStackTrace, ex.StackTrace);
jsonWriter.WritePropertyName(JsonPropertyNames.ExceptionInnerException);
{
WriteException(jsonWriter, ex.InnerException);
}
}
{
jsonWriter.WriteStartObject();
{
jsonWriter.WriteString(JsonPropertyNames.ExceptionName, ex.GetType().FullName);
jsonWriter.WriteString(JsonPropertyNames.ExceptionMessage, ex.Message);
jsonWriter.WriteString(JsonPropertyNames.ExceptionStackTrace, ex.StackTrace);
jsonWriter.WritePropertyName(JsonPropertyNames.ExceptionInnerException);
{
WriteException(jsonWriter, ex.InnerException);
}
}
jsonWriter.WriteEndObject();
}
}
Expand Down
95 changes: 94 additions & 1 deletion tests/ZLogger.Tests/JsonFormatTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Text;
using System.Text.Json;
Expand Down Expand Up @@ -54,6 +54,93 @@ public void FormatLogEntry_CustomMetadata()
doc.GetProperty("LogLevel").GetString().Should().Be("Debug");
}

[Fact]
public void FormatException_Custom()
{
const string exceptionProperty = "ex";

using var ms = new MemoryStream();

var loggerFactory = LoggerFactory.Create(x => x
.SetMinimumLevel(LogLevel.Debug)
.AddZLoggerStream(ms, options =>
{
var hashProp = JsonEncodedText.Encode("Hash");

options.UseJsonFormatter(formatter =>
{
formatter.ExceptionFormatter = (Utf8JsonWriter writer, Exception exception) =>
{
writer.WriteString(exceptionProperty, exception.ToString());
};
});
}));
var logger = loggerFactory.CreateLogger("test");

var exception = new Exception("ZLogger test exception");
logger.LogDebug(exception, exception.Message);

loggerFactory.Dispose();

using var sr = new StreamReader(new MemoryStream(ms.ToArray()), Encoding.UTF8);
var json = sr.ReadLine();

var doc = JsonDocument.Parse(json).RootElement;

doc.GetProperty(exceptionProperty).GetString().Should().Be(exception.ToString());
}

[Fact]
public void FormatPropertyKeyValues_Custom()
{
using var ms = new MemoryStream();

var loggerFactory = LoggerFactory.Create(x => x
.SetMinimumLevel(LogLevel.Debug)
.AddZLoggerStream(ms, options =>
{
var hashProp = JsonEncodedText.Encode("Hash");

options.UseJsonFormatter(formatter =>
{
formatter.PropertyKeyValuesFormatter = (Utf8JsonWriter writer, IZLoggerEntry entry) =>
{
for (var i = 0; i < entry.ParameterCount; i++)
{
if (entry.IsSupportUtf8ParameterKey)
{
var key = entry.GetParameterKey(i);
writer.WritePropertyName(key);
}
else
{
var key = entry.GetParameterKeyAsString(i);
writer.WritePropertyName(key);
}

var value = entry.GetParameterValue(i);
writer.WriteStringValue(value?.ToString());
}
};
});
}));
var logger = loggerFactory.CreateLogger("test");

var fooValue = new StringWrapper("foo");
var barValue = new StringWrapper("bar");
logger.LogDebug("{foo}{bar}", fooValue, barValue);

loggerFactory.Dispose();

using var sr = new StreamReader(new MemoryStream(ms.ToArray()), Encoding.UTF8);
var json = sr.ReadLine();

var doc = JsonDocument.Parse(json).RootElement;

doc.GetProperty("foo").GetString().Should().Be(fooValue.Value);
doc.GetProperty("bar").GetString().Should().Be(barValue.Value);
}

[Fact]
public void FormatLogEntry_ExcludeLogInfoProperties()
{
Expand Down Expand Up @@ -358,5 +445,11 @@ class MyClass
public int Call2(int x, int y) => x + y;
}

class StringWrapper(string value)
{
public string Value => value;

public override string ToString() => value;
}
}
}