Skip to content

Commit 98aca3d

Browse files
committed
Merge branch 'bugfix/null-value' into main
2 parents 7e09bc6 + 3606f9e commit 98aca3d

16 files changed

+115
-13
lines changed

CHANGELOG.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [1.4.0][1.4.0] - 2025-09-19
8+
9+
- Properties whose values are `null` are now always serialized with a `value` attribute. Thanks to @southernprogrammer for reporting this issue. See the [Null text](https://github.com/serilog-contrib/serilog-formatting-log4net#null-text) documentation in the README for more information.
10+
11+
Before (1.3.1)
12+
13+
```xml
14+
<log4net:properties>
15+
<log4net:data name="key" />
16+
</log4net:properties>
17+
```
18+
19+
After (1.4.0)
20+
21+
```xml
22+
<log4net:properties>
23+
<log4net:data name="key" value="(null)" />
24+
</log4net:properties>
25+
```
26+
727
## [1.3.1][1.3.1] - 2025-04-30
828

929
- Log events coming from `Microsoft.Extensions.Logging` are now identified if they have **either** an `EventId.Id` or an `EventId.Name` property. Previously, log events coming from `Microsoft.Extensions.Logging` were identified if they had **both** an `Id` and a `Name` property.
@@ -139,7 +159,8 @@ Still trying to figure out how to make everything fit together with [MinVer](htt
139159

140160
- Implement log4j compatibility mode.
141161

142-
[Unreleased]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.3.1...HEAD
162+
[Unreleased]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.4.0...HEAD
163+
[1.4.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.3.1...1.4.0
143164
[1.3.1]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.3.0...1.3.1
144165
[1.3.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.2.0...1.3.0
145166
[1.2.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.1.0...1.2.0

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@ By default, Log4NetTextFormatter writes all messages and exceptions with a [CDAT
106106
new Log4NetTextFormatter(c => c.UseCDataMode(CDataMode.Never))
107107
```
108108

109+
### Null text
110+
111+
Available since version 1.4.0
112+
113+
By default, when a property value is `null`, the text used for its representation is `(null)`. This can be changed with the `UseNullText()` configuration method.
114+
115+
Here's how to configure the formatter to use `🔷null🔷` when a property value is `null`.
116+
117+
```c#
118+
new Log4NetTextFormatter(c => c.UseNullText("🔷null🔷"));
119+
```
120+
121+
This will produce the following output.
122+
123+
```xml
124+
<log4net:properties>
125+
<log4net:data name="key" value="🔷null🔷" />
126+
</log4net:properties>
127+
```
128+
129+
To restore compatibility with version 1.3.1 and earlier (which produced a non-compatible XML), `UseNullText(null)` can be called.
130+
109131
### XML Namespace
110132

111133
You can remove the `log4net` XML namespace by calling `UseNoXmlNamespace()` on the options builder. This is useful if you want to spare some bytes and your log reader supports log4net XML events without namespace, like [Log4View](https://www.log4view.com) does.

src/Log4NetTextFormatter.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,11 +406,14 @@ private void WritePropertyElement(XmlWriter writer, string name, LogEventPropert
406406
{
407407
WriteStartElement(writer, "data");
408408
writer.WriteAttributeString("name", name);
409-
var isNullValue = value is ScalarValue { Value: null };
410-
if (!isNullValue)
409+
if (value is not ScalarValue { Value: null })
411410
{
412411
writer.WriteAttributeString("value", RenderValue(value));
413412
}
413+
else if (_options.NullText != null)
414+
{
415+
writer.WriteAttributeString("value", _options.NullText);
416+
}
414417
writer.WriteEndElement();
415418
}
416419

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: 22 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,23 @@ 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+
/// <remarks>
92+
/// Pass <see langword="null"/> for <paramref name="nullText"/> to omit the <c>value</c> attribute, i.e. <code>&lt;log4net:data name="MiddleName" /&gt;</code>
93+
/// Note that this is not strictly compatible with log4net/log4j XML formats and should be used to keep compatibility with Serilog.Formatting.Log4Net 1.3.1 and earlier if needed.
94+
/// </remarks>
95+
public Log4NetTextFormatterOptionsBuilder UseNullText(string? nullText)
96+
{
97+
_nullText = nullText;
98+
return this;
99+
}
100+
81101
/// <summary>
82102
/// Do not use any XML namespace for log4net events.
83103
/// <para/>
@@ -197,7 +217,7 @@ public void UseLog4JCompatibility()
197217
}
198218

199219
internal Log4NetTextFormatterOptions Build()
200-
=> new(_formatProvider, _cDataMode, _xmlNamespace, CreateXmlWriterSettings(_lineEnding, _indentationSettings), _filterProperty, _formatMessage, _formatException);
220+
=> new(_formatProvider, _cDataMode, _nullText, _xmlNamespace, CreateXmlWriterSettings(_lineEnding, _indentationSettings), _filterProperty, _formatMessage, _formatException);
201221

202222
private static XmlWriterSettings CreateXmlWriterSettings(LineEnding lineEnding, IndentationSettings? indentationSettings)
203223
{

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>
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>

tests/Log4NetTextFormatterTest.NullProperty.verified.xml renamed to tests/Log4NetTextFormatterTest.NullProperty_nullText=null.verified.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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/">
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/">
22
<log4net:properties>
33
<log4net:data name="n/a" />
44
</log4net:properties>

0 commit comments

Comments
 (0)