diff --git a/src/IndentationSettings.cs b/src/IndentationSettings.cs
index b050ba1..8140945 100644
--- a/src/IndentationSettings.cs
+++ b/src/IndentationSettings.cs
@@ -21,16 +21,20 @@ public IndentationSettings(Indentation indentation, byte size)
{
throw new ArgumentOutOfRangeException(nameof(size), size, $"The value of argument '{nameof(size)}' must be greater than 0.");
}
- _indentationString = indentation switch
+ (Character, _indentationString) = indentation switch
{
- Indentation.Space => new string(c: ' ', size),
- Indentation.Tab => new string(c: '\t', size),
+ Indentation.Space => (' ', new string(c: ' ', size)),
+ Indentation.Tab => ('\t', new string(c: '\t', size)),
_ => throw new ArgumentOutOfRangeException(nameof(indentation), indentation, $"The value of argument '{nameof(indentation)}' ({indentation}) is invalid for enum type '{nameof(Indentation)}'.")
};
+ Size = size;
}
///
/// Returns a string representation of the indentation settings.
///
public override string ToString() => _indentationString;
+
+ internal char Character { get; }
+ internal byte Size { get; }
}
\ No newline at end of file
diff --git a/src/Log4NetTextFormatter.cs b/src/Log4NetTextFormatter.cs
index a5099fe..1abeb5d 100644
--- a/src/Log4NetTextFormatter.cs
+++ b/src/Log4NetTextFormatter.cs
@@ -104,30 +104,10 @@ public void Format(LogEvent logEvent, TextWriter output)
{
throw new ArgumentNullException(nameof(output));
}
- var xmlWriterOutput = _usesLog4JCompatibility ? new StringWriter() : output;
- using var writer = XmlWriter.Create(xmlWriterOutput, _options.XmlWriterSettings);
+ using var writer = _options.CreateXmlWriter(output, _usesLog4JCompatibility);
WriteEvent(logEvent, writer);
writer.Flush();
- if (_usesLog4JCompatibility)
- {
- // log4j writes the XML "manually", see https://github.com/apache/log4j/blob/v1_2_17/src/main/java/org/apache/log4j/xml/XMLLayout.java#L137-L145
- // The resulting XML is impossible to write with a standard compliant XML writer such as XmlWriter.
- // That's why we write the event in a StringWriter then massage the output to remove the xmlns:log4j attribute to match log4j output.
- // The XML fragment becomes valid when surrounded by an external entity, see https://github.com/apache/log4j/blob/v1_2_17/src/main/java/org/apache/log4j/xml/XMLLayout.java#L31-L49
- const string log4JNamespaceAttribute = """
- xmlns:log4j="http://jakarta.apache.org/log4j/"
- """;
- var xmlString = ((StringWriter)xmlWriterOutput).ToString();
- var i = xmlString.IndexOf(log4JNamespaceAttribute, StringComparison.Ordinal);
-#if NETSTANDARD2_0
- output.Write(xmlString.Substring(0, i));
- output.Write(xmlString.Substring(i + log4JNamespaceAttribute.Length));
-#else
- output.Write(xmlString.AsSpan(0, i));
- output.Write(xmlString.AsSpan(i + log4JNamespaceAttribute.Length));
-#endif
- }
- output.Write(_options.XmlWriterSettings.NewLineChars);
+ output.Write(_options.NewLineChars);
}
///
diff --git a/src/Log4NetTextFormatterOptions.cs b/src/Log4NetTextFormatterOptions.cs
index 80f7ad5..0d7119b 100644
--- a/src/Log4NetTextFormatterOptions.cs
+++ b/src/Log4NetTextFormatterOptions.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Xml;
namespace Serilog.Formatting.Log4Net;
@@ -8,12 +9,13 @@ namespace Serilog.Formatting.Log4Net;
///
internal sealed class Log4NetTextFormatterOptions
{
- internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode cDataMode, XmlQualifiedName? xmlNamespace, XmlWriterSettings xmlWriterSettings, PropertyFilter filterProperty, ExceptionFormatter formatException)
+ internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode cDataMode, XmlQualifiedName? xmlNamespace, LineEnding lineEnding, IndentationSettings? indentationSettings, PropertyFilter filterProperty, ExceptionFormatter formatException)
{
FormatProvider = formatProvider;
CDataMode = cDataMode;
XmlNamespace = xmlNamespace;
- XmlWriterSettings = xmlWriterSettings;
+ NewLineChars = lineEnding.ToCharacters();
+ IndentationSettings = indentationSettings;
FilterProperty = filterProperty;
FormatException = formatException;
}
@@ -27,12 +29,42 @@ internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode
/// See
internal XmlQualifiedName? XmlNamespace { get; }
- /// See
- internal XmlWriterSettings XmlWriterSettings { get; }
+ /// See
+ internal string NewLineChars { get; }
+
+ /// See
+ private IndentationSettings? IndentationSettings { get; }
/// See
internal PropertyFilter FilterProperty { get; }
/// See
internal ExceptionFormatter FormatException { get; }
+
+ internal XmlWriter CreateXmlWriter(TextWriter output, bool useLog4JCompatibility)
+ {
+ if (useLog4JCompatibility)
+ {
+ var xmlWriter = new NoNamespaceXmlWriter(output, Log4NetTextFormatterOptionsBuilder.Log4JXmlNamespace);
+ if (IndentationSettings != null)
+ {
+ xmlWriter.Formatting = System.Xml.Formatting.Indented;
+ xmlWriter.IndentChar = IndentationSettings.Character;
+ xmlWriter.Indentation = IndentationSettings.Size;
+ }
+ return xmlWriter;
+ }
+
+ var settings = new XmlWriterSettings
+ {
+ Indent = IndentationSettings is not null,
+ NewLineChars = NewLineChars,
+ ConformanceLevel = ConformanceLevel.Fragment,
+ };
+ if (IndentationSettings is not null)
+ {
+ settings.IndentChars = IndentationSettings.ToString();
+ }
+ return XmlWriter.Create(output, settings);
+ }
}
\ No newline at end of file
diff --git a/src/Log4NetTextFormatterOptionsBuilder.cs b/src/Log4NetTextFormatterOptionsBuilder.cs
index 55d0a06..63cc846 100644
--- a/src/Log4NetTextFormatterOptionsBuilder.cs
+++ b/src/Log4NetTextFormatterOptionsBuilder.cs
@@ -177,22 +177,7 @@ public void UseLog4JCompatibility()
}
internal Log4NetTextFormatterOptions Build()
- => new(_formatProvider, _cDataMode, _xmlNamespace, CreateXmlWriterSettings(_lineEnding, _indentationSettings), _filterProperty, _formatException);
-
- private static XmlWriterSettings CreateXmlWriterSettings(LineEnding lineEnding, IndentationSettings? indentationSettings)
- {
- var xmlWriterSettings = new XmlWriterSettings
- {
- Indent = indentationSettings is not null,
- NewLineChars = lineEnding.ToCharacters(),
- ConformanceLevel = ConformanceLevel.Fragment,
- };
- if (indentationSettings is not null)
- {
- xmlWriterSettings.IndentChars = indentationSettings.ToString();
- }
- return xmlWriterSettings;
- }
+ => new(_formatProvider, _cDataMode, _xmlNamespace, _lineEnding, _indentationSettings, _filterProperty, _formatException);
}
///
diff --git a/src/NoNamespaceXmlWriter.cs b/src/NoNamespaceXmlWriter.cs
new file mode 100644
index 0000000..da82855
--- /dev/null
+++ b/src/NoNamespaceXmlWriter.cs
@@ -0,0 +1,51 @@
+using System.IO;
+using System.Xml;
+
+namespace Serilog.Formatting.Log4Net;
+
+// Inspired by https://www.hanselman.com/blog/xmlfragmentwriter-omiting-the-xml-declaration-and-the-xsd-and-xsi-namespaces but does not actually work since the xmlns stack can't be manipulated that easily.
+internal sealed class NoNamespaceXmlWriter : XmlTextWriter
+{
+ private readonly XmlQualifiedName _ns;
+ private bool _skipAttribute;
+
+ // log4j writes the XML "manually", see https://github.com/apache/log4j/blob/v1_2_17/src/main/java/org/apache/log4j/xml/XMLLayout.java#L137-L145
+ // The resulting XML is impossible to write with a standard compliant XML writer such as XmlWriter.
+ // That's why we write the event in a StringWriter then massage the output to remove the xmlns:log4j attribute to match log4j output.
+ // The XML fragment becomes valid when surrounded by an external entity, see https://github.com/apache/log4j/blob/v1_2_17/src/main/java/org/apache/log4j/xml/XMLLayout.java#L31-L49
+ public NoNamespaceXmlWriter(TextWriter output, XmlQualifiedName ns) : base(output)
+ {
+ _ns = ns;
+ }
+
+ public override void WriteEndAttribute()
+ {
+ if (_skipAttribute)
+ {
+ _skipAttribute = false;
+ }
+ else
+ {
+ base.WriteEndAttribute();
+ }
+ }
+
+ public override void WriteStartAttribute(string? prefix, string localName, string? ns)
+ {
+ // Actually that's not how writing XML namespaces work...
+ if (prefix == "xmlns" && localName == _ns.Name)
+ {
+ _skipAttribute = true;
+ }
+ else
+ {
+ base.WriteStartAttribute(prefix, localName, ns);
+ }
+ }
+
+ public override void WriteString(string? text)
+ {
+ if (!_skipAttribute)
+ base.WriteString(text);
+ }
+}
\ No newline at end of file
diff --git a/tests/Log4NetTextFormatterTest.BasicLog4J.verified.xml b/tests/Log4NetTextFormatterTest.BasicLog4J.verified.xml
new file mode 100644
index 0000000..81df489
--- /dev/null
+++ b/tests/Log4NetTextFormatterTest.BasicLog4J.verified.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/tests/Log4NetTextFormatterTest.cs b/tests/Log4NetTextFormatterTest.cs
index 97ff4d5..63480fa 100644
--- a/tests/Log4NetTextFormatterTest.cs
+++ b/tests/Log4NetTextFormatterTest.cs
@@ -324,6 +324,21 @@ public Task Log4JCompatibility(bool useStaticInstance)
return Verify(output).DisableRequireUniquePrefix();
}
+ [Fact]
+ public Task BasicLog4J()
+ {
+ // Arrange
+ using var output = new StringWriter();
+ var logEvent = CreateLogEvent();
+ var formatter = new Log4NetTextFormatter(c => c.UseLog4JCompatibility());
+
+ // Act
+ formatter.Format(logEvent, output);
+
+ // Assert
+ return Verify(output).DisableRequireUniquePrefix();
+ }
+
[Fact]
public Task ExplicitFormatProvider()
{