Skip to content

Commit

Permalink
XmlLayout - Render LogEventInfo.Properties as XML (Ex. SQL XML Column)
Browse files Browse the repository at this point in the history
  • Loading branch information
snakefoot committed Apr 23, 2018
1 parent f313565 commit ec65246
Show file tree
Hide file tree
Showing 8 changed files with 910 additions and 98 deletions.
22 changes: 22 additions & 0 deletions src/NLog/Internal/StringBuilderExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,5 +280,27 @@ internal static void AppendIntegerAsString(this StringBuilder sb, object value,
break;
}
}

internal static void TrimRight(this StringBuilder sb)
{
int i = sb.Length - 1;
for (; i >= 0; i--)
if (!char.IsWhiteSpace(sb[i]))
break;

if (i < sb.Length - 1)
sb.Length = i + 1;
}

internal static void TrimLeft(this StringBuilder sb)
{
int i = 0;
for (; i < sb.Length; i++)
if (!char.IsWhiteSpace(sb[i]))
break;

if (i > 0)
sb.Remove(0, i);
}
}
}
180 changes: 169 additions & 11 deletions src/NLog/Internal/XmlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,72 @@ private static string CreateValidXmlString(string text)
}
#endif

private static readonly char[] XmlEscapeChars = new char[] { '<', '>', '&', '\'', '"' };
private static readonly char[] XmlEscapeNewlineChars = new char[] { '<', '>', '&', '\'', '"', '\r', '\n' };

internal static string EscapeXmlString(string text, bool xmlEncodeNewlines, StringBuilder result = null)
{
if (result == null && text.Length < 4096 && text.IndexOfAny(xmlEncodeNewlines ? XmlEscapeNewlineChars : XmlEscapeChars) < 0)
return text;

var sb = result ?? new StringBuilder(text.Length);
for (int i = 0; i < text.Length; ++i)
{
switch (text[i])
{
case '<':
sb.Append("&lt;");
break;

case '>':
sb.Append("&gt;");
break;

case '&':
sb.Append("&amp;");
break;

case '\'':
sb.Append("&apos;");
break;

case '"':
sb.Append("&quot;");
break;

case '\r':
if (xmlEncodeNewlines)
sb.Append("&#13;");
else
sb.Append(text[i]);
break;

case '\n':
if (xmlEncodeNewlines)
sb.Append("&#10;");
else
sb.Append(text[i]);
break;

default:
sb.Append(text[i]);
break;
}
}

return result == null ? sb.ToString() : null;
}


/// <summary>
/// Converts object value to invariant format, and strips any invalid xml-characters
/// </summary>
/// <param name="value">Object value</param>
/// <returns>Object value converted to string</returns>
internal static string XmlConvertToStringSafe(object value)
{
string valueString = XmlConvertToString(value);
return RemoveInvalidXmlChars(valueString);
TypeCode objTypeCode = Convert.GetTypeCode(value);
return XmlConvertToString(value, objTypeCode, true);
}

/// <summary>
Expand All @@ -117,16 +174,118 @@ internal static string XmlConvertToStringSafe(object value)
internal static string XmlConvertToString(object value)
{
TypeCode objTypeCode = Convert.GetTypeCode(value);
return XmlConvertToString(value, objTypeCode);
return XmlConvertToString(value, objTypeCode, false);
}

/// <summary>
/// XML elements must follow these naming rules:
/// - Element names are case-sensitive
/// - Element names must start with a letter or underscore
/// - Element names cannot start with the letters xml(or XML, or Xml, etc)
/// - Element names can contain letters, digits, hyphens, underscores, and periods
/// - Element names cannot contain spaces
/// </summary>
/// <param name="xmlElementName"></param>
/// <param name="allowNamespace"></param>
internal static string XmlConvertToElementName(string xmlElementName, bool allowNamespace)
{
if (string.IsNullOrEmpty(xmlElementName))
return xmlElementName;

xmlElementName = RemoveInvalidXmlChars(xmlElementName);

StringBuilder sb = null;
for (int i = 0; i < xmlElementName.Length; ++i)
{
char chr = xmlElementName[i];
if (char.IsLetter(chr))
{
if (i == 0 && (chr == 'x' || chr == 'X') && xmlElementName.Length >= 3)
{
if (char.ToLowerInvariant(xmlElementName[1]) == 'm' && char.ToLowerInvariant(xmlElementName[2]) == 'l')
{
sb = new StringBuilder(xmlElementName.Length + 1);
sb.Append('_'); // Prefix with underscore
sb.Append(chr);
continue;
}
}
sb?.Append(chr);
continue;
}

if (i == 0)
{
if (chr == '_')
{
sb?.Append(chr);
continue;
}
}
else
{
switch (chr)
{
case ':': // namespace-delimeter
if (allowNamespace)
{
allowNamespace = false;
continue;
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
case '_':
case '.':
{
sb?.Append(chr);
continue;
}
}
}

if (sb == null)
{
sb = new StringBuilder(xmlElementName.Length);
if (i > 1)
sb.Append(xmlElementName, 0, i - 1);
if (i == 0 && char.IsWhiteSpace(chr))
{
for (; i < xmlElementName.Length - 1; ++i)
{
if (!char.IsWhiteSpace(xmlElementName[i + 1]))
break;
}
continue;
}
}
sb.Append('_');
}

if (sb != null)
{
sb.TrimRight();
}
return sb?.ToString() ?? xmlElementName;
}

/// <summary>
/// Converts object value to invariant format (understood by JavaScript)
/// </summary>
/// <param name="value">Object value</param>
/// <param name="objTypeCode">Object TypeCode</param>
/// <param name="safeConversion">Object TypeCode</param>
/// <returns>Object value converted to string</returns>
internal static string XmlConvertToString(object value, TypeCode objTypeCode)
internal static string XmlConvertToString(object value, TypeCode objTypeCode, bool safeConversion = false)
{
if (value == null)
{
Expand Down Expand Up @@ -176,11 +335,12 @@ internal static string XmlConvertToString(object value, TypeCode objTypeCode)
case TypeCode.Char:
return XmlConvert.ToString((char)value);
case TypeCode.String:
return (string)value;
return safeConversion ? RemoveInvalidXmlChars((string)value) : (string)value;
default:
try
{
return Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
string valueString = Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture);
return safeConversion ? RemoveInvalidXmlChars(valueString) : valueString;
}
catch
{
Expand All @@ -206,15 +366,13 @@ public static void WriteAttributeSafeString(this XmlWriter writer, string prefix
/// Safe version of WriteAttributeString
/// </summary>
/// <param name="writer"></param>
/// <param name="thread"></param>
/// <param name="localName"></param>
public static void WriteAttributeSafeString(this XmlWriter writer, string thread, string localName)
/// <param name="value"></param>
public static void WriteAttributeSafeString(this XmlWriter writer, string localName, string value)
{
writer.WriteAttributeString(RemoveInvalidXmlChars(thread), RemoveInvalidXmlChars(localName));
writer.WriteAttributeString(RemoveInvalidXmlChars(localName), RemoveInvalidXmlChars(value));
}



/// <summary>
/// Safe version of WriteElementSafeString
/// </summary>
Expand Down
44 changes: 20 additions & 24 deletions src/NLog/LayoutRenderers/Log4JXmlEventLayoutRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ protected override void InitializeLayoutRenderer()
{
Indent = IndentXml,
ConformanceLevel = ConformanceLevel.Fragment,
#if !NET3_5
NamespaceHandling = NamespaceHandling.OmitDuplicates,
#endif
IndentChars = " ",
};
}
Expand Down Expand Up @@ -257,7 +260,10 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent)
using (XmlWriter xtw = XmlWriter.Create(sb, _xmlWriterSettings))
{
xtw.WriteStartElement("log4j", "event", dummyNamespace);
xtw.WriteAttributeSafeString("xmlns", "nlog", null, dummyNLogNamespace);
if (IncludeNLogData && (IncludeCallSite || IncludeSourceInfo))
{
xtw.WriteAttributeSafeString("xmlns", "nlog", null, dummyNLogNamespace);
}
xtw.WriteAttributeSafeString("logger", LoggerName != null ? LoggerName.Render(logEvent) : logEvent.LoggerName);
xtw.WriteAttributeSafeString("level", logEvent.Level.Name.ToUpperInvariant());
xtw.WriteAttributeSafeString("timestamp", Convert.ToString((long)(logEvent.TimeStamp.ToUniversalTime() - log4jDateBase).TotalMilliseconds, CultureInfo.InvariantCulture));
Expand All @@ -266,23 +272,22 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent)
xtw.WriteElementSafeString("log4j", "message", dummyNamespace, logEvent.FormattedMessage);
if (logEvent.Exception != null)
{
// TODO Why twice the exception details?
xtw.WriteElementSafeString("log4j", "throwable", dummyNamespace, logEvent.Exception.ToString());
}

AppendNdc(xtw);

AppendException(logEvent, xtw);

AppendCallSite(logEvent, xtw);

AppendProperties(xtw);
xtw.WriteStartElement("log4j", "properties", dummyNamespace);

AppendMdc(xtw);

AppendMdlc(xtw);

if (IncludeAllProperties)
{
AppendProperties("log4j", xtw, logEvent);
AppendProperties("log4j", dummyNamespaceRemover, xtw, logEvent);
}

AppendParameters(logEvent, xtw);
Expand All @@ -297,14 +302,17 @@ protected override void Append(StringBuilder builder, LogEventInfo logEvent)
xtw.WriteAttributeSafeString("value", _machineName);
xtw.WriteEndElement();

xtw.WriteEndElement();
xtw.WriteEndElement(); // properties

xtw.WriteEndElement();
xtw.WriteEndElement(); // event
xtw.Flush();

// get rid of 'nlog' and 'log4j' namespace declarations
sb.Replace(dummyNamespaceRemover, string.Empty);
sb.Replace(dummyNLogNamespaceRemover, string.Empty);
if (IncludeNLogData && (IncludeCallSite || IncludeSourceInfo))
{
sb.Replace(dummyNLogNamespaceRemover, string.Empty);
}
sb.CopyTo(builder); // StringBuilder.Replace is not good when reusing the StringBuilder
}
}
Expand Down Expand Up @@ -356,17 +364,6 @@ private void AppendNdc(XmlWriter xtw)
}
}

private static void AppendException(LogEventInfo logEvent, XmlWriter xtw)
{
if (logEvent.Exception != null)
{
// TODO Why twice the exception details?
xtw.WriteStartElement("log4j", "throwable", dummyNamespace);
xtw.WriteSafeCData(logEvent.Exception.ToString());
xtw.WriteEndElement();
}
}

private void AppendParameters(LogEventInfo logEvent, XmlWriter xtw)
{
if (Parameters.Count > 0)
Expand All @@ -381,9 +378,8 @@ private void AppendParameters(LogEventInfo logEvent, XmlWriter xtw)
}
}

private void AppendProperties(XmlWriter xtw)
private void AppendMdc(XmlWriter xtw)
{
xtw.WriteStartElement("log4j", "properties", dummyNamespace);
if (IncludeMdc)
{
foreach (string key in MappedDiagnosticsContext.GetNames())
Expand Down Expand Up @@ -438,14 +434,14 @@ private void AppendCallSite(LogEventInfo logEvent, XmlWriter xtw)
xtw.WriteEndElement();

xtw.WriteStartElement("nlog", "properties", dummyNLogNamespace);
AppendProperties("nlog", xtw, logEvent);
AppendProperties("nlog", dummyNLogNamespace, xtw, logEvent);
xtw.WriteEndElement();
}
}
}
}

private void AppendProperties(string prefix, XmlWriter xtw, LogEventInfo logEvent)
private void AppendProperties(string prefix, string dummyNamespace, XmlWriter xtw, LogEventInfo logEvent)
{
if (logEvent.HasProperties)
{
Expand Down
Loading

0 comments on commit ec65246

Please sign in to comment.