Skip to content

Commit 8dae8cf

Browse files
committed
XmlLayout - Render LogEventInfo.Properties as XML (Support for singleton tag)
1 parent c8bdbff commit 8dae8cf

File tree

2 files changed

+53
-26
lines changed

2 files changed

+53
-26
lines changed

src/NLog/Layouts/XmlLayout.cs

+47-21
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,12 @@ public class XmlLayout : Layout
124124
/// <remarks>
125125
/// Support string-format where {0} means property-key-name
126126
///
127-
/// Skips closing element tag when having configured <see cref="PropertiesFormatValueAttribute"/>
127+
/// Skips closing element tag when having configured <see cref="PropertiesNodeValueAttribute"/>
128128
/// </remarks>
129129
/// <docgen category='LogEvent Properties XML Options' order='10' />
130-
public string PropertiesFormatElementName { get; set; } = "property";
130+
public string PropertiesNodeName { get { return _propertiesNodeName; } set { _propertiesNodeName = value; _propertiesNodeNameFormat = value?.IndexOf('{') >= 0; } }
131+
private string _propertiesNodeName = "property";
132+
private bool _propertiesNodeNameFormat;
131133

132134
/// <summary>
133135
/// XML attribute format to use when rendering property-key
@@ -138,7 +140,7 @@ public class XmlLayout : Layout
138140
/// Replaces newlines with underscore (_)
139141
/// </remarks>
140142
/// <docgen category='LogEvent Properties XML Options' order='10' />
141-
public string PropertiesFormatKeyAttribute { get; set; } = "key=\"{0}\"";
143+
public string PropertiesNodeKeyAttribute { get; set; } = "key=\"{0}\"";
142144

143145
/// <summary>
144146
/// XML attribute format to use when rendering property-value
@@ -153,7 +155,7 @@ public class XmlLayout : Layout
153155
/// Skips closing element tag when using attribute for value
154156
/// </remarks>
155157
/// <docgen category='LogEvent Properties XML Options' order='10' />
156-
public string PropertiesFormatValueAttribute { get; set; }
158+
public string PropertiesNodeValueAttribute { get; set; }
157159

158160
/// <summary>
159161
/// Initializes a new instance of the <see cref="XmlLayout"/> class.
@@ -219,8 +221,9 @@ protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuil
219221
RenderXmlFormattedMessage(logEvent, target);
220222
if (target.Length == orgLength && IncludeEmptyValue && !string.IsNullOrEmpty(NodeName))
221223
{
222-
BeginXmlDocument(target, NodeName);
223-
EndXmlDocument(target, NodeName);
224+
target.Append('<');
225+
target.Append(NodeName);
226+
target.Append("/>");
224227
}
225228
}
226229

@@ -252,10 +255,20 @@ private void RenderXmlFormattedMessage(LogEventInfo logEvent, StringBuilder sb)
252255
}
253256
if (sb.Length != orgLength)
254257
{
255-
sb.Append('>');
256-
if (IndentXml)
257-
sb.AppendLine();
258+
bool hasNodes = NodeValue != null || Nodes.Count > 0 || IncludeMdc || IncludeMdlc || (IncludeAllProperties && logEvent.HasProperties);
259+
if (!hasNodes)
260+
{
261+
sb.Append(" />");
262+
return;
263+
}
264+
else
265+
{
266+
sb.Append('>');
267+
if (IndentXml)
268+
sb.AppendLine();
269+
}
258270
}
271+
259272
if (NodeValue != null)
260273
{
261274
int beforeNodeLength = sb.Length;
@@ -331,12 +344,12 @@ private void RenderXmlFormattedMessage(LogEventInfo logEvent, StringBuilder sb)
331344

332345
private void AppendXmlPropertyValue(string propName, object propertyValue, StringBuilder sb, bool beginXmlDocument)
333346
{
334-
if (string.IsNullOrEmpty(PropertiesFormatElementName))
347+
if (string.IsNullOrEmpty(PropertiesNodeName))
335348
return; // Not supported
336349

337-
string xmlKeyString = XmlHelper.XmlConvertToElementName(propName?.Trim(), false);
338-
if (string.IsNullOrEmpty(xmlKeyString))
339-
return;
350+
propName = propName?.Trim();
351+
if (string.IsNullOrEmpty(propName))
352+
return; // Not supported
340353

341354
if (beginXmlDocument && !string.IsNullOrEmpty(NodeName))
342355
{
@@ -346,29 +359,42 @@ private void AppendXmlPropertyValue(string propName, object propertyValue, Strin
346359
if (IndentXml && !string.IsNullOrEmpty(NodeName))
347360
sb.Append(" ");
348361

349-
string xmlValueString = XmlHelper.XmlConvertToStringSafe(propertyValue);
350-
351362
sb.Append('<');
352-
sb.AppendFormat(PropertiesFormatElementName, xmlKeyString);
353-
if (!string.IsNullOrEmpty(PropertiesFormatKeyAttribute))
363+
string propNameElement = null;
364+
if (_propertiesNodeNameFormat)
365+
{
366+
propNameElement = XmlHelper.XmlConvertToStringSafe(propName);
367+
sb.AppendFormat(PropertiesNodeName, propNameElement);
368+
}
369+
else
354370
{
371+
sb.Append(PropertiesNodeName);
372+
}
373+
374+
if (!string.IsNullOrEmpty(PropertiesNodeKeyAttribute))
375+
{
376+
string propNameAttribute = ReferenceEquals(propName, propNameElement) ? propName : XmlHelper.EscapeXmlString(propName, true);
355377
sb.Append(' ');
356-
sb.AppendFormat(PropertiesFormatKeyAttribute, xmlKeyString);
378+
sb.AppendFormat(PropertiesNodeKeyAttribute, propNameAttribute);
357379
}
358380

359-
if (!string.IsNullOrEmpty(PropertiesFormatValueAttribute))
381+
string xmlValueString = XmlHelper.XmlConvertToStringSafe(propertyValue);
382+
if (!string.IsNullOrEmpty(PropertiesNodeValueAttribute))
360383
{
361384
xmlValueString = XmlHelper.EscapeXmlString(xmlValueString, true);
362385
sb.Append(' ');
363-
sb.AppendFormat(PropertiesFormatValueAttribute, xmlValueString);
386+
sb.AppendFormat(PropertiesNodeValueAttribute, xmlValueString);
364387
sb.Append(" />");
365388
}
366389
else
367390
{
368391
sb.Append('>');
369392
XmlHelper.EscapeXmlString(xmlValueString, false, sb);
370393
sb.Append("</");
371-
sb.AppendFormat(PropertiesFormatElementName, xmlKeyString);
394+
if (_propertiesNodeNameFormat)
395+
sb.AppendFormat(PropertiesNodeName, propNameElement);
396+
else
397+
sb.AppendFormat(PropertiesNodeName, PropertiesNodeName);
372398
sb.Append('>');
373399
}
374400
if (IndentXml)

tests/NLog.UnitTests/Layouts/XmlLayoutTests.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,13 @@ public void XmlLayoutLog4j()
7777
Nodes =
7878
{
7979
new XmlLayout("log4j:message", "${message}"),
80-
new XmlLayout("log4j:throwable", "${exception:format=tostring}") { IncludeEmptyValue = false },
80+
new XmlLayout("log4j:throwable", "${exception:format=tostring}"),
81+
new XmlLayout("log4j:locationInfo", null) { Attributes = { new XmlAttribute("class", "${callsite:methodName=false}") { IncludeEmptyValue = true } } },
8182
},
8283
};
83-
xmlLayout.PropertiesFormatElementName = "log4j:data";
84-
xmlLayout.PropertiesFormatKeyAttribute = "name=\"{0}\"";
85-
xmlLayout.PropertiesFormatValueAttribute = "value=\"{0}\"";
84+
xmlLayout.PropertiesNodeName = "log4j:data";
85+
xmlLayout.PropertiesNodeKeyAttribute = "name=\"{0}\"";
86+
xmlLayout.PropertiesNodeValueAttribute = "value=\"{0}\"";
8687
xmlLayout.IncludeAllProperties = true;
8788
xmlLayout.IncludeMdc = true;
8889
xmlLayout.IncludeMdlc = true;
@@ -97,7 +98,7 @@ public void XmlLayoutLog4j()
9798
var logEventInfo = LogEventInfo.Create(LogLevel.Debug, "A", null, null, "some message");
9899
logEventInfo.Properties["nlogPropertyKey"] = "<nlog\r\nPropertyValue>";
99100

100-
Assert.Equal(@"<log4j:event logger=""A"" level=""DEBUG""><log4j:message>some message</log4j:message><log4j:data name=""foo1"" value=""bar1"" /><log4j:data name=""foo2"" value=""bar2"" /><log4j:data name=""foo3"" value=""bar3"" /><log4j:data name=""nlogPropertyKey"" value=""&lt;nlog&#13;&#10;PropertyValue&gt;"" /></log4j:event>", xmlLayout.Render(logEventInfo));
101+
Assert.Equal(@"<log4j:event logger=""A"" level=""DEBUG""><log4j:message>some message</log4j:message><log4j:locationInfo class="""" /><log4j:data name=""foo1"" value=""bar1"" /><log4j:data name=""foo2"" value=""bar2"" /><log4j:data name=""foo3"" value=""bar3"" /><log4j:data name=""nlogPropertyKey"" value=""&lt;nlog&#13;&#10;PropertyValue&gt;"" /></log4j:event>", xmlLayout.Render(logEventInfo));
101102
}
102103
}
103104
}

0 commit comments

Comments
 (0)