Skip to content

Commit 1e19656

Browse files
committed
Always write the value XML attributes in properties, even when the value is null
Also make the null text configurable through a new `UseNullText()` method on the options builder. The default null text value is `(null)` in order to match the log4net behavior. Fixes #287
1 parent 52a1975 commit 1e19656

14 files changed

+73
-19
lines changed

src/Log4NetTextFormatter.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,7 @@ private void WritePropertyElement(XmlWriter writer, string name, LogEventPropert
406406
WriteStartElement(writer, "data");
407407
writer.WriteAttributeString("name", name);
408408
var isNullValue = value is ScalarValue { Value: null };
409-
if (!isNullValue)
410-
{
411-
writer.WriteAttributeString("value", RenderValue(value));
412-
}
409+
writer.WriteAttributeString("value", isNullValue ? _options.NullText : RenderValue(value));
413410
writer.WriteEndElement();
414411
}
415412

src/Log4NetTextFormatterOptions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ namespace Serilog.Formatting.Log4Net;
88
/// </summary>
99
internal sealed class Log4NetTextFormatterOptions
1010
{
11-
internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode cDataMode, XmlQualifiedName? xmlNamespace, XmlWriterSettings xmlWriterSettings, PropertyFilter filterProperty, MessageFormatter formatMessage, ExceptionFormatter formatException)
11+
internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode cDataMode, string nullText, XmlQualifiedName? xmlNamespace, XmlWriterSettings xmlWriterSettings, PropertyFilter filterProperty, MessageFormatter formatMessage, ExceptionFormatter formatException)
1212
{
1313
FormatProvider = formatProvider;
1414
CDataMode = cDataMode;
15+
NullText = nullText;
1516
XmlNamespace = xmlNamespace;
1617
XmlWriterSettings = xmlWriterSettings;
1718
FilterProperty = filterProperty;
@@ -25,6 +26,9 @@ internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode
2526
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseCDataMode"/></summary>
2627
internal CDataMode CDataMode { get; }
2728

29+
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseNullText"/></summary>
30+
internal string NullText { get; }
31+
2832
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseNoXmlNamespace"/></summary>
2933
internal XmlQualifiedName? XmlNamespace { get; }
3034

src/Log4NetTextFormatterOptionsBuilder.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ internal Log4NetTextFormatterOptionsBuilder()
3131
/// <summary>See <see cref="UseFormatProvider"/></summary>
3232
private IFormatProvider? _formatProvider;
3333

34-
/// <summary> See <see cref="UseCDataMode"/></summary>
34+
/// <summary>See <see cref="UseCDataMode"/></summary>
3535
private CDataMode _cDataMode = CDataMode.Always;
3636

37+
/// <summary>See <see cref="UseNullText"/></summary>
38+
private string _nullText = "(null)";
39+
3740
/// <summary>See <see cref="UseNoXmlNamespace"/></summary>
3841
private XmlQualifiedName? _xmlNamespace = Log4NetXmlNamespace;
3942

@@ -78,6 +81,19 @@ public Log4NetTextFormatterOptionsBuilder UseCDataMode(CDataMode cDataMode)
7881
return this;
7982
}
8083

84+
/// <summary>
85+
/// Sets how <see langword="null"/> properties are rendered as textual representation inside XML attributes.
86+
/// <para/>
87+
/// The default value is <c>(null)</c>.
88+
/// </summary>
89+
/// <param name="nullText">The text to use to represent <see langword="null"/> properties.</param>
90+
/// <returns>The builder in order to fluently chain all options.</returns>
91+
public Log4NetTextFormatterOptionsBuilder UseNullText(string nullText)
92+
{
93+
_nullText = nullText ?? throw new ArgumentNullException(nameof(nullText), "The null text can not be null.");
94+
return this;
95+
}
96+
8197
/// <summary>
8298
/// Do not use any XML namespace for log4net events.
8399
/// <para/>
@@ -197,7 +213,7 @@ public void UseLog4JCompatibility()
197213
}
198214

199215
internal Log4NetTextFormatterOptions Build()
200-
=> new(_formatProvider, _cDataMode, _xmlNamespace, CreateXmlWriterSettings(_lineEnding, _indentationSettings), _filterProperty, _formatMessage, _formatException);
216+
=> new(_formatProvider, _cDataMode, _nullText, _xmlNamespace, CreateXmlWriterSettings(_lineEnding, _indentationSettings), _filterProperty, _formatMessage, _formatException);
201217

202218
private static XmlWriterSettings CreateXmlWriterSettings(LineEnding lineEnding, IndentationSettings? indentationSettings)
203219
{

tests/Log4NetTextFormatterTest.DefaultMessageFormatter_eventId=1_eventIdName=EventName.verified.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<log4net:data name="Name" value="World" />
44
<log4net:data name="EventId.Id" value="1" />
55
<log4net:data name="EventId.Name" value="EventName" />
6-
<log4net:data name="EventId.More" />
6+
<log4net:data name="EventId.More" value="(null)" />
77
</log4net:properties>
88
<log4net:message><![CDATA[Hello World!]]></log4net:message>
99
</log4net:event>

tests/Log4NetTextFormatterTest.NullProperty.verified.xml

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
2+
<log4net:properties>
3+
<log4net:data name="n/a" value="&lt;null&gt;" />
4+
</log4net:properties>
5+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
6+
</log4net:event>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
2+
<log4net:properties>
3+
<log4net:data name="n/a" value="" />
4+
</log4net:properties>
5+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
6+
</log4net:event>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
2+
<log4net:properties>
3+
<log4net:data name="n/a" value="(null)" />
4+
</log4net:properties>
5+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
6+
</log4net:event>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
2+
<log4net:properties>
3+
<log4net:data name="n/a" value="🌀" />
4+
</log4net:properties>
5+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
6+
</log4net:event>

tests/Log4NetTextFormatterTest.TwoPropertiesOneNull.verified.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
22
<log4net:properties>
3-
<log4net:data name="n/a" />
3+
<log4net:data name="n/a" value="(null)" />
44
<log4net:data name="one" value="1" />
55
</log4net:properties>
66
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>

tests/Log4NetTextFormatterTest.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ public void NullOutputThrowsArgumentNullException()
8282
.Which.StackTrace!.TrimStart().Should().StartWith("at Serilog.Formatting.Log4Net.Log4NetTextFormatter.Format");
8383
}
8484

85+
[Fact]
86+
public void SettingNullTextToNullThrowsArgumentNullException()
87+
{
88+
// Act
89+
Action action = () => _ = new Log4NetTextFormatter(c => c.UseNullText(null!));
90+
91+
// Assert
92+
action.Should().ThrowExactly<ArgumentNullException>()
93+
.WithMessage("The null text can not be null.*")
94+
.And.ParamName.Should().Be("nullText");
95+
}
96+
8597
[Fact]
8698
public void SettingPropertyFilterToNullThrowsArgumentNullException()
8799
{
@@ -243,19 +255,23 @@ public Task NoNamespace()
243255
return Verify(output);
244256
}
245257

246-
[Fact]
247-
public Task NullProperty()
258+
[Theory]
259+
[InlineData(null)]
260+
[InlineData("")]
261+
[InlineData("<null>")]
262+
[InlineData("🌀")]
263+
public Task NullProperty(string? nullText)
248264
{
249265
// Arrange
250266
using var output = new StringWriter();
251267
var logEvent = CreateLogEvent(properties: new LogEventProperty("n/a", new ScalarValue(null)));
252-
var formatter = new Log4NetTextFormatter();
268+
var formatter = new Log4NetTextFormatter(options => { if (nullText != null) { options.UseNullText(nullText); } });
253269

254270
// Act
255271
formatter.Format(logEvent, output);
256272

257273
// Assert
258-
return Verify(output);
274+
return Verify(output).UseParameters(nullText);
259275
}
260276

261277
[Fact]

tests/PublicApi.net6.0.verified.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public void UseLog4JCompatibility() { }
4545
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseMessageFormatter(Serilog.Formatting.Log4Net.MessageFormatter formatMessage) { }
4646
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNoIndentation() { }
4747
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNoXmlNamespace() { }
48+
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNullText(string nullText) { }
4849
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UsePropertyFilter(Serilog.Formatting.Log4Net.PropertyFilter filterProperty) { }
4950
}
5051
public delegate string MessageFormatter(Serilog.Events.LogEvent logEvent, System.IFormatProvider? formatProvider);

tests/PublicApi.net8.0.verified.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public void UseLog4JCompatibility() { }
4646
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseMessageFormatter(Serilog.Formatting.Log4Net.MessageFormatter formatMessage) { }
4747
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNoIndentation() { }
4848
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNoXmlNamespace() { }
49+
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNullText(string nullText) { }
4950
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UsePropertyFilter(Serilog.Formatting.Log4Net.PropertyFilter filterProperty) { }
5051
}
5152
public delegate string MessageFormatter(Serilog.Events.LogEvent logEvent, System.IFormatProvider? formatProvider);

tests/PublicApi.netstandard2.0.verified.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public void UseLog4JCompatibility() { }
4545
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseMessageFormatter(Serilog.Formatting.Log4Net.MessageFormatter formatMessage) { }
4646
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNoIndentation() { }
4747
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNoXmlNamespace() { }
48+
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UseNullText(string nullText) { }
4849
public Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder UsePropertyFilter(Serilog.Formatting.Log4Net.PropertyFilter filterProperty) { }
4950
}
5051
public delegate string MessageFormatter(Serilog.Events.LogEvent logEvent, System.IFormatProvider? formatProvider);

0 commit comments

Comments
 (0)