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

JsonLayout - Added ExcludeEmptyProperties to skip GDC/MDC/MLDC properties with null or empty values #4221

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/NLog/Layouts/JsonLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ public JsonLayout()
[DefaultValue(false)]
public bool IncludeAllProperties { get; set; }

/// <summary>
/// Gets or sets the option to exclude null/empty properties from the log event (as JSON)
/// </summary>
/// <docgen category='JSON Output' order='10' />
[DefaultValue(false)]
public bool ExcludeEmptyProperties { get; set; }

/// <summary>
/// List of property names to exclude when <see cref="IncludeAllProperties"/> is true
/// </summary>
Expand Down Expand Up @@ -278,6 +285,7 @@ private void RenderJsonFormattedMessage(LogEventInfo logEvent, StringBuilder sb)
if (string.IsNullOrEmpty(key))
continue;
object propertyValue = GlobalDiagnosticsContext.GetObject(key);

AppendJsonPropertyValue(key, propertyValue, null, null, MessageTemplates.CaptureType.Unknown, sb, sb.Length == orgLength);
}
}
Expand All @@ -289,6 +297,7 @@ private void RenderJsonFormattedMessage(LogEventInfo logEvent, StringBuilder sb)
if (string.IsNullOrEmpty(key))
continue;
object propertyValue = MappedDiagnosticsContext.GetObject(key);

AppendJsonPropertyValue(key, propertyValue, null, null, MessageTemplates.CaptureType.Unknown, sb, sb.Length == orgLength);
}
}
Expand Down Expand Up @@ -353,6 +362,11 @@ private void CompleteJsonMessage(StringBuilder sb)

private void AppendJsonPropertyValue(string propName, object propertyValue, string format, IFormatProvider formatProvider, MessageTemplates.CaptureType captureType, StringBuilder sb, bool beginJsonMessage)
{
if (ExcludeEmptyProperties && propertyValue == null)
return;

var initialLength = sb.Length;

BeginJsonProperty(sb, propName, beginJsonMessage);
if (MaxRecursionLimit <= 1 && captureType == MessageTemplates.CaptureType.Serialize)
{
Expand All @@ -370,6 +384,11 @@ private void AppendJsonPropertyValue(string propName, object propertyValue, stri
{
JsonConverter.SerializeObject(propertyValue, sb);
}

if (ExcludeEmptyProperties && (sb[sb.Length-1] == '"' && sb[sb.Length-2] == '"'))
{
sb.Length = initialLength;
}
}

private static void PerformJsonEscapeIfNeeded(StringBuilder sb, int valueStart, bool escapeForwardSlash)
Expand Down
154 changes: 154 additions & 0 deletions tests/NLog.UnitTests/Layouts/JsonLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace NLog.UnitTests.Layouts
public class JsonLayoutTests : NLogTestBase
{
private const string ExpectedIncludeAllPropertiesWithExcludes = "{ \"StringProp\": \"ValueA\", \"IntProp\": 123, \"DoubleProp\": 123.123, \"DecimalProp\": 123.123, \"BoolProp\": true, \"NullProp\": null, \"DateTimeProp\": \"2345-01-23T12:34:56Z\" }";
private const string ExpectedExcludeEmptyPropertiesWithExcludes = "{ \"StringProp\": \"ValueA\", \"IntProp\": 123, \"DoubleProp\": 123.123, \"DecimalProp\": 123.123, \"BoolProp\": true, \"DateTimeProp\": \"2345-01-23T12:34:56Z\", \"NoEmptyProp4\": \"hello\" }";

[Fact]
public void JsonLayoutRendering()
Expand Down Expand Up @@ -454,6 +455,28 @@ public void PropertyKeyWithQuote()
Assert.Equal(@"{ ""fo\""o"": ""bar"" }", jsonLayout.Render(logEventInfo));
}

[Fact]
public void ExcludeEmptyJsonProperties()
{
var jsonLayout = new JsonLayout()
{
IncludeAllProperties = true,
ExcludeEmptyProperties = true
};

jsonLayout.ExcludeProperties.Add("Excluded1");
jsonLayout.ExcludeProperties.Add("Excluded2");

var logEventInfo = CreateLogEventWithExcluded();
logEventInfo.Properties.Add("EmptyProp", "");
logEventInfo.Properties.Add("EmptyProp1", null);
logEventInfo.Properties.Add("EmptyProp2", new DummyContextLogger() { Value = null });
logEventInfo.Properties.Add("EmptyProp3", new DummyContextLogger() { Value = "" });
logEventInfo.Properties.Add("NoEmptyProp4", new DummyContextLogger() { Value = "hello" });

Assert.Equal(ExpectedExcludeEmptyPropertiesWithExcludes, jsonLayout.Render(logEventInfo));
}

[Fact]
public void IncludeAllJsonPropertiesMaxRecursionLimit()
{
Expand Down Expand Up @@ -511,6 +534,47 @@ public void IncludeMdcJsonProperties()
AssertDebugLastMessage("debug", ExpectedIncludeAllPropertiesWithExcludes);
}

[Fact]
public void IncludeMdcNoEmptyJsonProperties()
{
LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(@"
<nlog throwExceptions='true'>
<targets>
<target name='asyncDebug' type='AsyncWrapper' timeToSleepBetweenBatches='0'>
<target name='debug' type='Debug' >
<layout type=""JsonLayout"" IncludeMdc='true' ExcludeProperties='Excluded1,Excluded2' ExcludeEmptyProperties='true'>
</layout>
</target>
</target>
</targets>
<rules>
<logger name='*' minlevel='Debug' writeTo='asyncDebug' />
</rules>
</nlog>");

ILogger logger = LogManager.GetLogger("A");

var logEventInfo = CreateLogEventWithExcluded();
logEventInfo.Properties.Add("EmptyProp", "");
logEventInfo.Properties.Add("EmptyProp1", null);
logEventInfo.Properties.Add("EmptyProp2", new DummyContextLogger() { Value = null });
logEventInfo.Properties.Add("EmptyProp3", new DummyContextLogger() { Value = "" });
logEventInfo.Properties.Add("NoEmptyProp4", new DummyContextLogger() { Value = "hello" });

MappedDiagnosticsContext.Clear();
foreach (var prop in logEventInfo.Properties)
if (prop.Key.ToString() != "Excluded1" && prop.Key.ToString() != "Excluded2")
MappedDiagnosticsContext.Set(prop.Key.ToString(), prop.Value);
logEventInfo.Properties.Clear();


logger.Debug(logEventInfo);

LogManager.Flush();

AssertDebugLastMessage("debug", ExpectedExcludeEmptyPropertiesWithExcludes);
}

[Fact]
public void IncludeGdcJsonProperties()
{
Expand Down Expand Up @@ -546,6 +610,46 @@ public void IncludeGdcJsonProperties()
AssertDebugLastMessage("debug", ExpectedIncludeAllPropertiesWithExcludes);
}

[Fact]
public void IncludeGdcNoEmptyJsonProperties()
{
LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(@"
<nlog throwExceptions='true'>
<targets>
<target name='asyncDebug' type='AsyncWrapper' timeToSleepBetweenBatches='0'>
<target name='debug' type='Debug' >
<layout type=""JsonLayout"" IncludeGdc='true' ExcludeProperties='Excluded1,Excluded2' ExcludeEmptyProperties='true'>
</layout>
</target>
</target>
</targets>
<rules>
<logger name='*' minlevel='Debug' writeTo='asyncDebug' />
</rules>
</nlog>");

ILogger logger = LogManager.GetLogger("A");

var logEventInfo = CreateLogEventWithExcluded();
logEventInfo.Properties.Add("EmptyProp", "");
logEventInfo.Properties.Add("EmptyProp1", null);
logEventInfo.Properties.Add("EmptyProp2", new DummyContextLogger() { Value = null });
logEventInfo.Properties.Add("EmptyProp3", new DummyContextLogger() { Value = "" });
logEventInfo.Properties.Add("NoEmptyProp4", new DummyContextLogger() { Value = "hello" });

GlobalDiagnosticsContext.Clear();
foreach (var prop in logEventInfo.Properties)
if (prop.Key.ToString() != "Excluded1" && prop.Key.ToString() != "Excluded2")
GlobalDiagnosticsContext.Set(prop.Key.ToString(), prop.Value);
logEventInfo.Properties.Clear();

logger.Debug(logEventInfo);

LogManager.Flush();

AssertDebugLastMessage("debug", ExpectedExcludeEmptyPropertiesWithExcludes);
}

[Fact]
public void IncludeMdlcJsonProperties()
{
Expand Down Expand Up @@ -581,6 +685,46 @@ public void IncludeMdlcJsonProperties()
AssertDebugLastMessage("debug", ExpectedIncludeAllPropertiesWithExcludes);
}

[Fact]
public void IncludeMdlcNoEmptyJsonProperties()
{
LogManager.Configuration = XmlLoggingConfiguration.CreateFromXmlString(@"
<nlog throwExceptions='true'>
<targets>
<target name='asyncDebug' type='AsyncWrapper' timeToSleepBetweenBatches='0'>
<target name='debug' type='Debug' >
<layout type=""JsonLayout"" IncludeMdlc='true' ExcludeProperties='Excluded1,Excluded2' ExcludeEmptyProperties='true'>
</layout>
</target>
</target>
</targets>
<rules>
<logger name='*' minlevel='Debug' writeTo='asyncDebug' />
</rules>
</nlog>");

ILogger logger = LogManager.GetLogger("A");

var logEventInfo = CreateLogEventWithExcluded();
logEventInfo.Properties.Add("EmptyProp", "");
logEventInfo.Properties.Add("EmptyProp1", null);
logEventInfo.Properties.Add("EmptyProp2", new DummyContextLogger() { Value = null });
logEventInfo.Properties.Add("EmptyProp3", new DummyContextLogger() { Value = "" });
logEventInfo.Properties.Add("NoEmptyProp4", new DummyContextLogger() { Value = "hello" });

MappedDiagnosticsLogicalContext.Clear();
foreach (var prop in logEventInfo.Properties)
if (prop.Key.ToString() != "Excluded1" && prop.Key.ToString() != "Excluded2")
MappedDiagnosticsLogicalContext.Set(prop.Key.ToString(), prop.Value);
logEventInfo.Properties.Clear();

logger.Debug(logEventInfo);

LogManager.Flush();

AssertDebugLastMessage("debug", ExpectedExcludeEmptyPropertiesWithExcludes);
}

[Fact]
public void IncludeMdlcJsonNestedProperties()
{
Expand Down Expand Up @@ -918,5 +1062,15 @@ private static LogEventInfo CreateLogEventWithExcluded()
logEventInfo.Properties.Add("Excluded2", "Also excluded");
return logEventInfo;
}

public class DummyContextLogger
{
internal string Value { get; set; }

public override string ToString()
{
return Value;
}
}
}
}