Skip to content

Commit 14eaabc

Browse files
committed
[WIP] Add support for Serilog.Settings.Configuration
TODO: finish writing the README and update the CHANGELOG
1 parent 6e8eda1 commit 14eaabc

10 files changed

+454
-40
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ Log.Logger = new LoggerConfiguration()
216216
.CreateLogger();
217217
```
218218

219+
## Configuration with [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration)
220+
221+
Available since version 2.0.0
222+
223+
> [!TIP]
224+
> The the message formatter, the exception formatter and the property filter can only be configured through code. Use the `Serilog.Settings.Configuration` integration only if you don't need them.
225+
219226
## Enrichers
220227

221228
The log4Net XML format defines some special attributes which are not included by default in Serilog events. They can be added by using the appropriate Serilog [enrichers](https://github.com/serilog/serilog/wiki/Enrichment).
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Globalization;
4+
using System.Linq;
5+
6+
namespace Serilog.Formatting.Log4Net;
7+
8+
// Support for https://github.com/serilog/serilog-settings-configuration through dedicated constructor
9+
public partial class Log4NetTextFormatter
10+
{
11+
/// <summary>
12+
/// The default text used to represent <see langword="null"/>.
13+
/// </summary>
14+
internal const string DefaultNullText = "(null)";
15+
16+
/// <summary>
17+
/// Do not use this constructor. It is only available for the <a href="https://github.com/serilog/serilog-settings-configuration">Serilog.Settings.Configuration</a> integration.
18+
/// The property filter, the message formatter and the exception formatter can only be configured through
19+
/// the <see cref="Log4NetTextFormatter(Action&lt;Log4NetTextFormatterOptionsBuilder&gt;)"/> constructor.
20+
/// </summary>
21+
/// <remarks>
22+
/// Binary compatibility across versions is not guaranteed. New optional parameters will be added in order to match new options.
23+
/// </remarks>
24+
[Obsolete("This constructor is only for use by the Serilog.Settings.Configuration package.", error: true)]
25+
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used by Serilog.Settings.Configuration through reflection.")]
26+
public Log4NetTextFormatter(
27+
string? formatProvider = null,
28+
CDataMode? cDataMode = null,
29+
string? nullText = DefaultNullText,
30+
// in order to support options.UseNullText(null) on .NET < 10, see https://learn.microsoft.com/en-us/dotnet/core/compatibility/extensions/10.0/configuration-null-values-preserved
31+
bool noNullText = false,
32+
bool noXmlNamespace = false,
33+
LineEnding? lineEnding = null,
34+
string? indentation = null,
35+
bool log4JCompatibility = false
36+
) : this(options =>
37+
{
38+
if (formatProvider != null)
39+
options.UseFormatProvider(CultureInfo.GetCultureInfo(formatProvider));
40+
if (cDataMode != null)
41+
options.UseCDataMode(cDataMode.Value);
42+
if (nullText != DefaultNullText)
43+
options.UseNullText(nullText);
44+
if (noNullText)
45+
options.UseNullText(null);
46+
if (noXmlNamespace)
47+
options.UseNoXmlNamespace();
48+
if (lineEnding != null)
49+
options.UseLineEnding(lineEnding.Value);
50+
if (indentation != null)
51+
ConfigureIndentation(options, indentation);
52+
if (log4JCompatibility)
53+
options.UseLog4JCompatibility();
54+
})
55+
{
56+
}
57+
58+
private static void ConfigureIndentation(Log4NetTextFormatterOptionsBuilder options, string indentation)
59+
{
60+
if (indentation.Length == 0)
61+
{
62+
options.UseNoIndentation();
63+
return;
64+
}
65+
66+
if (!ConfigureIndentation(options, indentation, Indentation.Space) && !ConfigureIndentation(options, indentation, Indentation.Tab))
67+
{
68+
throw new ArgumentException("The indentation must contains only space or tab characters.", nameof(indentation));
69+
}
70+
}
71+
72+
[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "An uppercase word in the middle of a sentence would be weird")]
73+
private static bool ConfigureIndentation(Log4NetTextFormatterOptionsBuilder options, string indentation, Indentation indent)
74+
{
75+
var indentChar = indent == Indentation.Space ? ' ' : '\t';
76+
var count = indentation.Count(c => c == indentChar);
77+
if (count == indentation.Length)
78+
{
79+
if (count <= byte.MaxValue)
80+
{
81+
options.UseIndentationSettings(new IndentationSettings(indent, Convert.ToByte(count)));
82+
return true;
83+
}
84+
85+
var indentDescription = $"{indent.ToString().ToLowerInvariant()}s";
86+
throw new ArgumentException($"The indentation exceeds the maximum number of allowed {indentDescription}. ({count} > {byte.MaxValue})", nameof(indentation));
87+
}
88+
89+
return false;
90+
}
91+
}

src/Log4NetTextFormatter.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ namespace Serilog.Formatting.Log4Net;
1414
/// <summary>
1515
/// A text formatter that serialize log events into <a href="https://logging.apache.org/log4net/">log4net</a> or <a href="https://logging.apache.org/log4j/">log4j</a> compatible XML format.
1616
/// </summary>
17-
// ReSharper disable once PartialTypeWithSinglePart -- Used for [GeneratedRegex] on .NET 8 onwards
1817
public partial class Log4NetTextFormatter : ITextFormatter
1918
{
2019
/// <summary>

src/Log4NetTextFormatterOptions.cs

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,20 @@ namespace Serilog.Formatting.Log4Net;
66
/// <summary>
77
/// Options for configuring the XML format produced by <see cref="Log4NetTextFormatter"/>.
88
/// </summary>
9-
internal sealed class Log4NetTextFormatterOptions
10-
{
11-
internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode cDataMode, string? nullText, XmlQualifiedName? xmlNamespace, XmlWriterSettings xmlWriterSettings, PropertyFilter filterProperty, MessageFormatter formatMessage, ExceptionFormatter formatException)
12-
{
13-
FormatProvider = formatProvider;
14-
CDataMode = cDataMode;
15-
NullText = nullText;
16-
XmlNamespace = xmlNamespace;
17-
XmlWriterSettings = xmlWriterSettings;
18-
FilterProperty = filterProperty;
19-
FormatMessage = formatMessage;
20-
FormatException = formatException;
21-
}
22-
23-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseFormatProvider"/></summary>
24-
internal IFormatProvider? FormatProvider { get; }
25-
26-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseCDataMode"/></summary>
27-
internal CDataMode CDataMode { get; }
28-
29-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseNullText"/></summary>
30-
internal string? NullText { get; }
31-
32-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseNoXmlNamespace"/></summary>
33-
internal XmlQualifiedName? XmlNamespace { get; }
34-
35-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.CreateXmlWriterSettings"/></summary>
36-
internal XmlWriterSettings XmlWriterSettings { get; }
37-
38-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UsePropertyFilter"/></summary>
39-
internal PropertyFilter FilterProperty { get; }
40-
41-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseMessageFormatter"/></summary>
42-
internal MessageFormatter FormatMessage { get; }
43-
44-
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseExceptionFormatter"/></summary>
45-
internal ExceptionFormatter FormatException { get; }
46-
}
9+
/// <param name="FormatProvider">See <see cref="Log4NetTextFormatterOptionsBuilder.UseFormatProvider"/></param>
10+
/// <param name="CDataMode">See <see cref="Log4NetTextFormatterOptionsBuilder.UseCDataMode"/></param>
11+
/// <param name="NullText">See <see cref="Log4NetTextFormatterOptionsBuilder.UseNullText"/></param>
12+
/// <param name="XmlNamespace">See <see cref="Log4NetTextFormatterOptionsBuilder.UseNoXmlNamespace"/></param>
13+
/// <param name="XmlWriterSettings">See <see cref="Log4NetTextFormatterOptionsBuilder.CreateXmlWriterSettings"/></param>
14+
/// <param name="FilterProperty">See <see cref="Log4NetTextFormatterOptionsBuilder.UsePropertyFilter"/></param>
15+
/// <param name="FormatMessage">See <see cref="Log4NetTextFormatterOptionsBuilder.UseMessageFormatter"/></param>
16+
/// <param name="FormatException">See <see cref="Log4NetTextFormatterOptionsBuilder.UseExceptionFormatter"/></param>
17+
internal record struct Log4NetTextFormatterOptions(
18+
IFormatProvider? FormatProvider,
19+
CDataMode CDataMode,
20+
string? NullText,
21+
XmlQualifiedName? XmlNamespace,
22+
XmlWriterSettings XmlWriterSettings,
23+
PropertyFilter FilterProperty,
24+
MessageFormatter FormatMessage,
25+
ExceptionFormatter FormatException);

src/Log4NetTextFormatterOptionsBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal Log4NetTextFormatterOptionsBuilder()
3535
private CDataMode _cDataMode = CDataMode.Always;
3636

3737
/// <summary>See <see cref="UseNullText"/></summary>
38-
private string? _nullText = "(null)";
38+
private string? _nullText = Log4NetTextFormatter.DefaultNullText;
3939

4040
/// <summary>See <see cref="UseNoXmlNamespace"/></summary>
4141
private XmlQualifiedName? _xmlNamespace = Log4NetXmlNamespace;

src/Serilog.Formatting.Log4Net.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@
5757
<None Update="packages.lock.json" Visible="false" />
5858
</ItemGroup>
5959

60+
<ItemGroup Label="Testing">
61+
<InternalsVisibleTo Include="Serilog.Formatting.Log4Net.Tests" />
62+
</ItemGroup>
63+
6064
<ItemGroup>
6165
<PackageReference Include="MinVer" Version="6.0.0" PrivateAssets="all" />
6266
<PackageReference Include="Serilog" Version="2.0.0" />

tests/PublicApi.net8.0.verified.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[assembly: System.CLSCompliant(true)]
22
[assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")]
33
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/serilog-contrib/serilog-formatting-log4net")]
4+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Serilog.Formatting.Log4Net.Tests")]
45
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName=".NET 8.0")]
56
namespace Serilog.Formatting.Log4Net
67
{
@@ -32,6 +33,8 @@ public class Log4NetTextFormatter : Serilog.Formatting.ITextFormatter
3233
{
3334
public Log4NetTextFormatter() { }
3435
public Log4NetTextFormatter(System.Action<Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder>? configureOptions) { }
36+
[System.Obsolete("This constructor is only for use by the Serilog.Settings.Configuration package.", true)]
37+
public Log4NetTextFormatter(string? formatProvider = null, Serilog.Formatting.Log4Net.CDataMode? cDataMode = default, string? nullText = "(null)", bool noNullText = false, bool noXmlNamespace = false, Serilog.Formatting.Log4Net.LineEnding? lineEnding = default, string? indentation = null, bool log4JCompatibility = false) { }
3538
public static Serilog.Formatting.Log4Net.Log4NetTextFormatter Log4JFormatter { get; }
3639
public void Format(Serilog.Events.LogEvent logEvent, System.IO.TextWriter output) { }
3740
}

tests/PublicApi.netstandard2.0.verified.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[assembly: System.CLSCompliant(true)]
22
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/serilog-contrib/serilog-formatting-log4net")]
3+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Serilog.Formatting.Log4Net.Tests")]
34
[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")]
45
namespace Serilog.Formatting.Log4Net
56
{
@@ -31,6 +32,8 @@ public class Log4NetTextFormatter : Serilog.Formatting.ITextFormatter
3132
{
3233
public Log4NetTextFormatter() { }
3334
public Log4NetTextFormatter(System.Action<Serilog.Formatting.Log4Net.Log4NetTextFormatterOptionsBuilder>? configureOptions) { }
35+
[System.Obsolete("This constructor is only for use by the Serilog.Settings.Configuration package.", true)]
36+
public Log4NetTextFormatter(string? formatProvider = null, Serilog.Formatting.Log4Net.CDataMode? cDataMode = default, string? nullText = "(null)", bool noNullText = false, bool noXmlNamespace = false, Serilog.Formatting.Log4Net.LineEnding? lineEnding = default, string? indentation = null, bool log4JCompatibility = false) { }
3437
public static Serilog.Formatting.Log4Net.Log4NetTextFormatter Log4JFormatter { get; }
3538
public void Format(Serilog.Events.LogEvent logEvent, System.IO.TextWriter output) { }
3639
}

tests/Serilog.Formatting.Log4Net.Tests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,23 @@
88
<ItemGroup>
99
<PackageReference Include="AwesomeAssertions" Version="9.2.1" />
1010
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="all" />
11+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
1112
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
13+
<PackageReference Include="NuGet.Versioning" Version="6.14.0" />
1214
<PackageReference Include="PublicApiGenerator" Version="11.5.0" />
15+
<PackageReference Include="ReflectionMagic" Version="5.0.1" />
1316
<PackageReference Include="ReportGenerator" Version="5.4.17" PrivateAssets="all" />
1417
<PackageReference Include="Serilog" Version="4.3.0" />
1518
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
1619
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
20+
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
21+
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
22+
<PackageReference Include="Tomlyn.Extensions.Configuration" Version="1.0.6" />
1723
<PackageReference Include="Verify" Version="31.0.4" GeneratePathProperty="true" />
1824
<PackageReference Include="Verify.Xunit" Version="31.0.4" />
1925
<PackageReference Include="xunit" Version="2.9.3" />
2026
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" PrivateAssets="all" />
27+
<PackageReference Include="Xunit.SkippableFact" Version="1.5.23" />
2128
</ItemGroup>
2229

2330
<ItemGroup>

0 commit comments

Comments
 (0)