diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15a2787..6995179 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@ This project adheres to [Semantic Versioning](http://semver.org/) and is followi
### :syringe: Fixed
- [#42](https://github.com/FantasticFiasco/serilog-sinks-udp/pull/42) Revert to support IPv4 on networks without IPv6 (contribution by [brettdavis-bmw](https://github.com/brettdavis-bmw))
-- [#45](https://github.com/FantasticFiasco/serilog-sinks-udp/pull/45) Correctly XML escape exception message serialized by `Log4jTextFormatter`
+- [#45](https://github.com/FantasticFiasco/serilog-sinks-udp/issues/45) Correctly XML escape exception message serialized by `Log4jTextFormatter`
+- [#51](https://github.com/FantasticFiasco/serilog-sinks-udp/issues/51) Correctly XML escape all properties serialized by `Log4jTextFormatter` and `Log4netTextFormatter`
### :dizzy: Changed
diff --git a/src/Serilog.Sinks.Udp/Sinks/Udp/Private/RemoteEndPoint.cs b/src/Serilog.Sinks.Udp/Sinks/Udp/Private/RemoteEndPoint.cs
index 5c03bf1..9d844c4 100644
--- a/src/Serilog.Sinks.Udp/Sinks/Udp/Private/RemoteEndPoint.cs
+++ b/src/Serilog.Sinks.Udp/Sinks/Udp/Private/RemoteEndPoint.cs
@@ -36,6 +36,14 @@ public RemoteEndPoint(string address, int port)
public int Port { get; }
+ ///
+ /// It's a very small performance optimization to parse the IP address and use it instead
+ /// of having the HTTP client trying to resolve the address and figure out that it isn't a
+ /// hostname at all but instead an ordinary IP address.
+ ///
+ /// A small optimization indeed, but one that was requested by one of the consumers of the
+ /// package.
+ ///
public IPEndPoint IPEndPoint { get; }
}
}
diff --git a/src/Serilog.Sinks.Udp/Sinks/Udp/Private/XmlSerializer.cs b/src/Serilog.Sinks.Udp/Sinks/Udp/Private/XmlSerializer.cs
new file mode 100644
index 0000000..3dd65df
--- /dev/null
+++ b/src/Serilog.Sinks.Udp/Sinks/Udp/Private/XmlSerializer.cs
@@ -0,0 +1,115 @@
+// Copyright 2015-2019 Serilog Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.IO;
+using System.Text;
+
+namespace Serilog.Sinks.Udp.Private
+{
+ ///
+ /// The methods in this class where influenced by
+ /// https://weblog.west-wind.com/posts/2018/Nov/30/Returning-an-XML-Encoded-String-in-NET.
+ ///
+ internal class XmlSerializer
+ {
+ private const char LtCharacter = '<';
+ private const char GtCharacter = '>';
+ private const char AmpCharacter = '&';
+ private const char QuotCharacter = '\"';
+ private const char AposCharacter = '\'';
+ private const char LfCharacter = '\n';
+ private const char CrCharacter = '\r';
+ private const char TabCharacter = '\t';
+
+ private const string LfString = "\n";
+ private const string CrString = "\r";
+ private const string TabString = "\t";
+
+ private const string SerializedLt = "<";
+ private const string SerializedGt = ">";
+ private const string SerializedAmp = "&";
+ private const string SerializedQuot = """;
+ private const string SerializedApos = "'";
+ private const string SerializedLf = "
";
+ private const string SerializedCr = "
";
+ private const string SerializedTab = " ";
+
+ internal void SerializeXmlValue(TextWriter output, string text, bool isAttribute)
+ {
+ foreach (var character in text)
+ {
+ output.Write(SerializeXmlValue(character, isAttribute));
+ }
+ }
+
+ internal string SerializeXmlValue(string text, bool isAttribute)
+ {
+ var builder = new StringBuilder();
+
+ foreach (var character in text)
+ {
+ builder.Append(SerializeXmlValue(character, isAttribute));
+ }
+
+ return builder.ToString();
+ }
+
+ private static string SerializeXmlValue(char character, bool isAttribute)
+ {
+ if (character == LtCharacter)
+ {
+ return SerializedLt;
+ }
+
+ if (character == GtCharacter)
+ {
+ return SerializedGt;
+ }
+
+ if (character == AmpCharacter)
+ {
+ return SerializedAmp;
+ }
+
+ // Special handling for quotes
+ if (isAttribute && character == QuotCharacter)
+ {
+ return SerializedQuot;
+ }
+
+ if (isAttribute && character == AposCharacter)
+ {
+ return SerializedApos;
+ }
+
+ // Legal sub-chr32 characters
+ if (character == LfCharacter)
+ {
+ return isAttribute ? SerializedLf : LfString;
+ }
+
+ if (character == CrCharacter)
+ {
+ return isAttribute ? SerializedCr : CrString;
+ }
+
+ if (character == TabCharacter)
+ {
+ return isAttribute ? SerializedTab : TabString;
+ }
+
+ return character.ToString();
+ }
+ }
+}
diff --git a/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4jTextFormatter.cs b/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4jTextFormatter.cs
index 949d127..f51f663 100644
--- a/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4jTextFormatter.cs
+++ b/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4jTextFormatter.cs
@@ -1,11 +1,11 @@
// Copyright 2015-2019 Serilog Contributors
-//
+//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
-//
+//
// http://www.apache.org/licenses/LICENSE-2.0
-//
+//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,17 +16,28 @@
using Serilog.Formatting;
using System.IO;
using System.Xml;
+using Serilog.Sinks.Udp.Private;
namespace Serilog.Sinks.Udp.TextFormatters
{
///
- /// Text formatter serializing log events into log4j complient XML.
+ /// Text formatter serializing log events into log4j compliant XML.
///
public class Log4jTextFormatter : ITextFormatter
{
private static readonly string SourceContextPropertyName = "SourceContext";
private static readonly string ThreadIdPropertyName = "ThreadId";
+ private readonly XmlSerializer xmlSerializer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Log4jTextFormatter()
+ {
+ xmlSerializer = new XmlSerializer();
+ }
+
///
/// Format the log event into the output.
///
@@ -49,11 +60,12 @@ public void Format(LogEvent logEvent, TextWriter output)
output.Write("");
}
- private static void WriteLogger(LogEvent logEvent, TextWriter output)
+ private void WriteLogger(LogEvent logEvent, TextWriter output)
{
- if (logEvent.Properties.TryGetValue(SourceContextPropertyName, out LogEventPropertyValue sourceContext))
+ if (logEvent.Properties.TryGetValue(SourceContextPropertyName, out var sourceContext))
{
- output.Write($" logger=\"{((ScalarValue)sourceContext).Value}\"");
+ var sourceContextValue = ((ScalarValue)sourceContext).Value.ToString();
+ output.Write($" logger=\"{xmlSerializer.SerializeXmlValue(sourceContextValue, true)}\"");
}
}
@@ -98,22 +110,23 @@ private static void WriteLevel(LogEvent logEvent, TextWriter output)
output.Write($" level=\"{level}\"");
}
- private static void WriteThread(LogEvent logEvent, TextWriter output)
+ private void WriteThread(LogEvent logEvent, TextWriter output)
{
- if (logEvent.Properties.TryGetValue(ThreadIdPropertyName, out LogEventPropertyValue threadId))
+ if (logEvent.Properties.TryGetValue(ThreadIdPropertyName, out var threadId))
{
- output.Write($" thread=\"{((ScalarValue)threadId).Value}\"");
+ var threadIdValue = ((ScalarValue)threadId).Value.ToString();
+ output.Write($" thread=\"{xmlSerializer.SerializeXmlValue(threadIdValue, true)}\"");
}
}
- private static void WriteMessage(LogEvent logEvent, TextWriter output)
+ private void WriteMessage(LogEvent logEvent, TextWriter output)
{
output.Write("");
- logEvent.RenderMessage(output);
+ xmlSerializer.SerializeXmlValue(output, logEvent.RenderMessage(), false);
output.Write("");
}
- private static void WriteException(LogEvent logEvent, TextWriter output)
+ private void WriteException(LogEvent logEvent, TextWriter output)
{
if (logEvent.Exception == null)
{
@@ -121,39 +134,8 @@ private static void WriteException(LogEvent logEvent, TextWriter output)
}
output.Write("");
- EscapeXmlPropertyValue(output, logEvent.Exception.ToString());
+ xmlSerializer.SerializeXmlValue(output, logEvent.Exception.ToString(), false);
output.Write("");
}
-
- ///
- /// This method has been influenced by
- /// https://weblog.west-wind.com/posts/2018/Nov/30/Returning-an-XML-Encoded-String-in-NET
- /// and does not support XML attribute values. The method in the article does, but this
- /// doesn't.
- ///
- private static void EscapeXmlPropertyValue(TextWriter output, string text)
- {
- foreach (var character in text)
- {
- switch (character)
- {
- case '<':
- output.Write("<");
- break;
-
- case '>':
- output.Write(">");
- break;
-
- case '&':
- output.Write("&");
- break;
-
- default:
- output.Write(character);
- break;
- }
- }
- }
}
}
diff --git a/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4netTextFormatter.cs b/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4netTextFormatter.cs
index 32d08e9..2c5d33b 100644
--- a/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4netTextFormatter.cs
+++ b/src/Serilog.Sinks.Udp/Sinks/Udp/TextFormatters/Log4netTextFormatter.cs
@@ -1,11 +1,11 @@
// Copyright 2015-2019 Serilog Contributors
-//
+//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
-//
+//
// http://www.apache.org/licenses/LICENSE-2.0
-//
+//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,6 +15,7 @@
using Serilog.Events;
using Serilog.Formatting;
using System.IO;
+using Serilog.Sinks.Udp.Private;
namespace Serilog.Sinks.Udp.TextFormatters
{
@@ -25,10 +26,20 @@ public class Log4netTextFormatter : ITextFormatter
{
private static readonly string SourceContextPropertyName = "SourceContext";
private static readonly string ThreadIdPropertyName = "ThreadId";
- private static readonly string MachineNamePropertyName = "MachineName";
private static readonly string UserNamePropertyName = "EnvironmentUserName";
- private static readonly string MethodPropertyName = "Method";
private static readonly string ProcessNamePropertyName = "ProcessName";
+ private static readonly string MethodPropertyName = "Method";
+ private static readonly string MachineNamePropertyName = "MachineName";
+
+ private readonly XmlSerializer xmlSerializer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Log4netTextFormatter()
+ {
+ xmlSerializer = new XmlSerializer();
+ }
///
/// Format the log event into the output.
@@ -62,51 +73,12 @@ public void Format(LogEvent logEvent, TextWriter output)
output.Write("");
}
- private static void WriteProcessName(LogEvent logEvent, TextWriter output)
- {
- if (logEvent.Properties.TryGetValue(ProcessNamePropertyName, out LogEventPropertyValue processName))
- {
- output.Write($" domain=\"{((ScalarValue)processName).Value}\"");
- }
- }
-
- private static void WriteLocationInfoClass(LogEvent logEvent, TextWriter output)
- {
- if (logEvent.Properties.TryGetValue(SourceContextPropertyName, out LogEventPropertyValue sourceContext))
- {
- output.Write($" class=\"{((ScalarValue)sourceContext).Value}\"");
- }
- }
-
- private static void WriteLocationInfoMethod(LogEvent logEvent, TextWriter output)
- {
- if (logEvent.Properties.TryGetValue(MethodPropertyName, out LogEventPropertyValue methodName))
- {
- output.Write($" method=\"{((ScalarValue)methodName).Value}\"");
- }
- }
-
- private static void WriteLogger(LogEvent logEvent, TextWriter output)
- {
- if (logEvent.Properties.TryGetValue(SourceContextPropertyName, out LogEventPropertyValue sourceContext))
- {
- output.Write($" logger=\"{((ScalarValue)sourceContext).Value}\"");
- }
- }
-
- private static void WriteUserName(LogEvent logEvent, TextWriter output)
+ private void WriteLogger(LogEvent logEvent, TextWriter output)
{
- if (logEvent.Properties.TryGetValue(UserNamePropertyName, out LogEventPropertyValue userName))
+ if (logEvent.Properties.TryGetValue(SourceContextPropertyName, out var sourceContext))
{
- output.Write($" username=\"{((ScalarValue)userName).Value}\"");
- }
- }
-
- private static void WriteHostName(LogEvent logEvent, TextWriter output)
- {
- if (logEvent.Properties.TryGetValue(MachineNamePropertyName, out LogEventPropertyValue machineName))
- {
- output.Write($" ");
+ var sourceContextValue = ((ScalarValue)sourceContext).Value.ToString();
+ output.Write($" logger=\"{xmlSerializer.SerializeXmlValue(sourceContextValue, true)}\"");
}
}
@@ -150,29 +122,77 @@ private static void WriteLevel(LogEvent logEvent, TextWriter output)
output.Write($" level=\"{level}\"");
}
- private static void WriteThread(LogEvent logEvent, TextWriter output)
+ private void WriteThread(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.Properties.TryGetValue(ThreadIdPropertyName, out var threadId))
+ {
+ var threadIdValue = ((ScalarValue)threadId).Value.ToString();
+ output.Write($" thread=\"{xmlSerializer.SerializeXmlValue(threadIdValue, true)}\"");
+ }
+ }
+
+ private void WriteUserName(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.Properties.TryGetValue(UserNamePropertyName, out var userName))
+ {
+ var userNameValue = ((ScalarValue)userName).Value.ToString();
+ output.Write($" username=\"{xmlSerializer.SerializeXmlValue(userNameValue, true)}\"");
+ }
+ }
+
+ private void WriteProcessName(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.Properties.TryGetValue(ProcessNamePropertyName, out var processName))
+ {
+ var processNameValue = ((ScalarValue)processName).Value.ToString();
+ output.Write($" domain=\"{xmlSerializer.SerializeXmlValue(processNameValue, true)}\"");
+ }
+ }
+
+ private void WriteLocationInfoClass(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.Properties.TryGetValue(SourceContextPropertyName, out var sourceContext))
+ {
+ var sourceContextValue = ((ScalarValue)sourceContext).Value.ToString();
+ output.Write($" class=\"{xmlSerializer.SerializeXmlValue(sourceContextValue, true)}\"");
+ }
+ }
+
+ private void WriteLocationInfoMethod(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.Properties.TryGetValue(MethodPropertyName, out var methodName))
+ {
+ var methodNameValue = ((ScalarValue)methodName).Value.ToString();
+ output.Write($" method=\"{xmlSerializer.SerializeXmlValue(methodNameValue, true)}\"");
+ }
+ }
+
+ private void WriteHostName(LogEvent logEvent, TextWriter output)
{
- if (logEvent.Properties.TryGetValue(ThreadIdPropertyName, out LogEventPropertyValue threadId))
+ if (logEvent.Properties.TryGetValue(MachineNamePropertyName, out var machineName))
{
- output.Write($" thread=\"{((ScalarValue)threadId).Value}\"");
+ var machineNameValue = ((ScalarValue)machineName).Value.ToString();
+ output.Write($" ");
}
}
- private static void WriteMessage(LogEvent logEvent, TextWriter output)
+ private void WriteMessage(LogEvent logEvent, TextWriter output)
{
output.Write("");
- logEvent.RenderMessage(output);
+ xmlSerializer.SerializeXmlValue(output, logEvent.RenderMessage(), false);
output.Write("");
}
- private static void WriteException(LogEvent logEvent, TextWriter output)
+ private void WriteException(LogEvent logEvent, TextWriter output)
{
if (logEvent.Exception == null)
{
return;
}
- output.Write($"{logEvent.Exception}");
+ output.Write("");
+ xmlSerializer.SerializeXmlValue(output, logEvent.Exception.ToString(), false);
+ output.Write("");
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4jTextFormatterShould.cs b/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4jTextFormatterShould.cs
index 8ec8098..cd95d32 100644
--- a/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4jTextFormatterShould.cs
+++ b/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4jTextFormatterShould.cs
@@ -35,6 +35,32 @@ public void WriteLoggerAttribute()
Deserialize().Root.Attribute("logger").Value.ShouldBe("source context");
}
+ [Theory]
+ [InlineData("Some < source context", "Some < source context")]
+ [InlineData("Some > source context", "Some > source context")]
+ [InlineData("Some & source context", "Some & source context")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" source context", "Some " source context")]
+ [InlineData("Some ' source context", "Some ' source context")]
+ [InlineData("Some \n source context", "Some
source context")]
+ [InlineData("Some \r source context", "Some
source context")]
+ [InlineData("Some \t source context", "Some source context")]
+ public void WriteEscapedLoggerAttribute(string sourceContext, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("SourceContext", new ScalarValue(sourceContext)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" logger=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Attribute("logger").Value.ShouldBe(sourceContext);
+ }
+
[Fact]
public void WriteTimestampAttribute()
{
@@ -76,6 +102,32 @@ public void WriteTheadAttribute()
Deserialize().Root.Attribute("thread").Value.ShouldBe("1");
}
+ [Theory]
+ [InlineData("Some < thread", "Some < thread")]
+ [InlineData("Some > thread", "Some > thread")]
+ [InlineData("Some & thread", "Some & thread")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" thread", "Some " thread")]
+ [InlineData("Some ' thread", "Some ' thread")]
+ [InlineData("Some \n thread", "Some
thread")]
+ [InlineData("Some \r thread", "Some
thread")]
+ [InlineData("Some \t thread", "Some thread")]
+ public void WriteEscapedTheadAttribute(string thread, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("ThreadId", new ScalarValue(thread)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" thread=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Attribute("thread").Value.ShouldBe(thread);
+ }
+
[Fact]
public void WriteMessageElement()
{
@@ -89,6 +141,36 @@ public void WriteMessageElement()
Deserialize().Root.Element(Namespace + "message").Value.ShouldBe("Some message");
}
+ [Theory]
+ [InlineData("Some < message", "Some < message")]
+ [InlineData("Some > message", "Some > message")]
+ [InlineData("Some & message", "Some & message")]
+ // The following characters should not be escaped in a XML element
+ [InlineData("Some \" message", "Some \" message")]
+ [InlineData("Some ' message", "Some ' message")]
+ [InlineData("Some \n message", "Some \n message")]
+ [InlineData("Some \r message", "Some \r message")]
+ [InlineData("Some \t message", "Some \t message")]
+ public void WriteEscapedMessageElement(string message, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent(message: message);
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($"{expected}");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form.
+ //
+ // "\r" are deserialized into "\n" by the .NET XML serializer, thus we need to
+ // compensate for that.
+ message = message.Replace("\r", "\n");
+
+ Deserialize().Root.Element(Namespace + "message").Value.ShouldBe(message);
+ }
+
[Fact]
public void WriteExceptionElement()
{
@@ -103,12 +185,15 @@ public void WriteExceptionElement()
}
[Theory]
- [InlineData("<", "<")]
- [InlineData(">", ">")]
- [InlineData("&", "&")]
- // The following characters should not be escaped in the error message
- [InlineData("\"", "\"")]
- [InlineData("'", "'")]
+ [InlineData("Some < message", "Some < message")]
+ [InlineData("Some > message", "Some > message")]
+ [InlineData("Some & message", "Some & message")]
+ // The following characters should not be escaped in a XML element
+ [InlineData("Some \" message", "Some \" message")]
+ [InlineData("Some ' message", "Some ' message")]
+ [InlineData("Some \n message", "Some \n message")]
+ [InlineData("Some \r message", "Some \r message")]
+ [InlineData("Some \t message", "Some \t message")]
public void WriteEscapedExceptionElement(string message, string expected)
{
// Arrange
@@ -119,6 +204,14 @@ public void WriteEscapedExceptionElement(string message, string expected)
// Assert
output.ToString().ShouldContain($"System.DivideByZeroException: {expected}");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form.
+ //
+ // "\r" are deserialized into "\n" by the .NET XML serializer, thus we need to
+ // compensate for that.
+ message = message.Replace("\r", "\n");
+
+ Deserialize().Root.Element(Namespace + "throwable").Value.ShouldBe($"System.DivideByZeroException: {message}");
}
private XDocument Deserialize()
diff --git a/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4netTextFormatterShould.cs b/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4netTextFormatterShould.cs
index 9c52093..26f84ca 100644
--- a/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4netTextFormatterShould.cs
+++ b/test/Serilog.Sinks.Udp.Tests/Sinks/Udp/TextFormatters/Log4netTextFormatterShould.cs
@@ -35,6 +35,32 @@ public void WriteLoggerAttribute()
Deserialize().Root.Attribute("logger").Value.ShouldBe("source context");
}
+ [Theory]
+ [InlineData("Some < source context", "Some < source context")]
+ [InlineData("Some > source context", "Some > source context")]
+ [InlineData("Some & source context", "Some & source context")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" source context", "Some " source context")]
+ [InlineData("Some ' source context", "Some ' source context")]
+ [InlineData("Some \n source context", "Some
source context")]
+ [InlineData("Some \r source context", "Some
source context")]
+ [InlineData("Some \t source context", "Some source context")]
+ public void WriteEscapedLoggerAttribute(string sourceContext, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("SourceContext", new ScalarValue(sourceContext)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" logger=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Attribute("logger").Value.ShouldBe(sourceContext);
+ }
+
[Fact]
public void WriteTimestampAttribute()
{
@@ -76,6 +102,32 @@ public void WriteTheadAttribute()
Deserialize().Root.Attribute("thread").Value.ShouldBe("1");
}
+ [Theory]
+ [InlineData("Some < thread", "Some < thread")]
+ [InlineData("Some > thread", "Some > thread")]
+ [InlineData("Some & thread", "Some & thread")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" thread", "Some " thread")]
+ [InlineData("Some ' thread", "Some ' thread")]
+ [InlineData("Some \n thread", "Some
thread")]
+ [InlineData("Some \r thread", "Some
thread")]
+ [InlineData("Some \t thread", "Some thread")]
+ public void WriteEscapedTheadAttribute(string thread, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("ThreadId", new ScalarValue(thread)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" thread=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Attribute("thread").Value.ShouldBe(thread);
+ }
+
[Fact]
public void WriteUsernameAttribute()
{
@@ -90,18 +142,70 @@ public void WriteUsernameAttribute()
Deserialize().Root.Attribute("username").Value.ShouldBe("some user");
}
+ [Theory]
+ [InlineData("Some < username", "Some < username")]
+ [InlineData("Some > username", "Some > username")]
+ [InlineData("Some & username", "Some & username")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" username", "Some " username")]
+ [InlineData("Some ' username", "Some ' username")]
+ [InlineData("Some \n username", "Some
username")]
+ [InlineData("Some \r username", "Some
username")]
+ [InlineData("Some \t username", "Some username")]
+ public void WriteEscapedUsernameAttribute(string username, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("EnvironmentUserName", new ScalarValue(username)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" username=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Attribute("username").Value.ShouldBe(username);
+ }
+
[Fact]
public void WriteDomainAttribute()
{
// Arrange
var logEvent = Some.LogEvent();
- logEvent.AddOrUpdateProperty(new LogEventProperty("ProcessName", new ScalarValue("some domain")));
+ logEvent.AddOrUpdateProperty(new LogEventProperty("ProcessName", new ScalarValue("process name")));
// Act
formatter.Format(logEvent, output);
// Assert
- Deserialize().Root.Attribute("domain").Value.ShouldBe("some domain");
+ Deserialize().Root.Attribute("domain").Value.ShouldBe("process name");
+ }
+
+ [Theory]
+ [InlineData("Some < process name", "Some < process name")]
+ [InlineData("Some > process name", "Some > process name")]
+ [InlineData("Some & process name", "Some & process name")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" process name", "Some " process name")]
+ [InlineData("Some ' process name", "Some ' process name")]
+ [InlineData("Some \n process name", "Some
process name")]
+ [InlineData("Some \r process name", "Some
process name")]
+ [InlineData("Some \t process name", "Some process name")]
+ public void WriteEscapedDomainAttribute(string processName, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("ProcessName", new ScalarValue(processName)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" domain=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Attribute("domain").Value.ShouldBe(processName);
}
[Fact]
@@ -118,6 +222,32 @@ public void WriteClassAttribute()
Deserialize().Root.Element(Namespace + "locationInfo").Attribute("class").Value.ShouldBe("source context");
}
+ [Theory]
+ [InlineData("Some < source context", "Some < source context")]
+ [InlineData("Some > source context", "Some > source context")]
+ [InlineData("Some & source context", "Some & source context")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" source context", "Some " source context")]
+ [InlineData("Some ' source context", "Some ' source context")]
+ [InlineData("Some \n source context", "Some
source context")]
+ [InlineData("Some \r source context", "Some
source context")]
+ [InlineData("Some \t source context", "Some source context")]
+ public void WriteEscapedClassAttribute(string sourceContext, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("SourceContext", new ScalarValue(sourceContext)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" class=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Element(Namespace + "locationInfo").Attribute("class").Value.ShouldBe(sourceContext);
+ }
+
[Fact]
public void WriteMethodAttribute()
{
@@ -131,9 +261,35 @@ public void WriteMethodAttribute()
// Assert
Deserialize().Root.Element(Namespace + "locationInfo").Attribute("method").Value.ShouldBe("Void Method()");
}
-
+
+ [Theory]
+ [InlineData("Some < method", "Some < method")]
+ [InlineData("Some > method", "Some > method")]
+ [InlineData("Some & method", "Some & method")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" method", "Some " method")]
+ [InlineData("Some ' method", "Some ' method")]
+ [InlineData("Some \n method", "Some
method")]
+ [InlineData("Some \r method", "Some
method")]
+ [InlineData("Some \t method", "Some method")]
+ public void WriteEscapedMethodAttribute(string method, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("Method", new ScalarValue(method)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" method=\"{expected}\"");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Element(Namespace + "locationInfo").Attribute("method").Value.ShouldBe(method);
+ }
+
[Fact]
- public void WriteMachineNameAttribute()
+ public void WriteHostNameAttribute()
{
// Arrange
var logEvent = Some.LogEvent();
@@ -146,6 +302,32 @@ public void WriteMachineNameAttribute()
Deserialize().Root.Element(Namespace + "properties").Element(Namespace + "data").Attribute("value").Value.ShouldBe("MachineName");
}
+ [Theory]
+ [InlineData("Some < hostname", "Some < hostname")]
+ [InlineData("Some > hostname", "Some > hostname")]
+ [InlineData("Some & hostname", "Some & hostname")]
+ // The following characters should be escaped in a XML attribute
+ [InlineData("Some \" hostname", "Some " hostname")]
+ [InlineData("Some ' hostname", "Some ' hostname")]
+ [InlineData("Some \n hostname", "Some
hostname")]
+ [InlineData("Some \r hostname", "Some
hostname")]
+ [InlineData("Some \t hostname", "Some hostname")]
+ public void WriteEscapedHostNameAttribute(string hostname, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent();
+ logEvent.AddOrUpdateProperty(new LogEventProperty("MachineName", new ScalarValue(hostname)));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($" ");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form
+ Deserialize().Root.Element(Namespace + "properties").Element(Namespace + "data").Attribute("value").Value.ShouldBe(hostname);
+ }
+
[Fact]
public void WriteMessageElement()
{
@@ -159,6 +341,36 @@ public void WriteMessageElement()
Deserialize().Root.Element(Namespace + "message").Value.ShouldBe("Some message");
}
+ [Theory]
+ [InlineData("Some < message", "Some < message")]
+ [InlineData("Some > message", "Some > message")]
+ [InlineData("Some & message", "Some & message")]
+ // The following characters should not be escaped in a XML element
+ [InlineData("Some \" message", "Some \" message")]
+ [InlineData("Some ' message", "Some ' message")]
+ [InlineData("Some \n message", "Some \n message")]
+ [InlineData("Some \r message", "Some \r message")]
+ [InlineData("Some \t message", "Some \t message")]
+ public void WriteEscapedMessageElement(string message, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent(message: message);
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($"{expected}");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form.
+ //
+ // "\r" are deserialized into "\n" by the .NET XML serializer, thus we need to
+ // compensate for that.
+ message = message.Replace("\r", "\n");
+
+ Deserialize().Root.Element(Namespace + "message").Value.ShouldBe(message);
+ }
+
[Fact]
public void WriteExceptionElement()
{
@@ -172,6 +384,36 @@ public void WriteExceptionElement()
Deserialize().Root.Element(Namespace + "throwable").Value.ShouldNotBeNull();
}
+ [Theory]
+ [InlineData("Some < message", "Some < message")]
+ [InlineData("Some > message", "Some > message")]
+ [InlineData("Some & message", "Some & message")]
+ // The following characters should not be escaped in a XML element
+ [InlineData("Some \" message", "Some \" message")]
+ [InlineData("Some ' message", "Some ' message")]
+ [InlineData("Some \n message", "Some \n message")]
+ [InlineData("Some \r message", "Some \r message")]
+ [InlineData("Some \t message", "Some \t message")]
+ public void WriteEscapedExceptionElement(string message, string expected)
+ {
+ // Arrange
+ var logEvent = Some.LogEvent(exception: new DivideByZeroException(message));
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ output.ToString().ShouldContain($"System.DivideByZeroException: {expected}");
+
+ // Lets make sure that the escaped XML can be deserialized back into its original form.
+ //
+ // "\r" are deserialized into "\n" by the .NET XML serializer, thus we need to
+ // compensate for that.
+ message = message.Replace("\r", "\n");
+
+ Deserialize().Root.Element(Namespace + "throwable").Value.ShouldBe($"System.DivideByZeroException: {message}");
+ }
+
private XDocument Deserialize()
{
return XDocument.Parse(output.ToString());