Skip to content

Commit 0c9c410

Browse files
committed
[WIP] Constructor for Serilog.Settings.Configuration integration
TODO: Restore 100% coverage
1 parent 6abd8f4 commit 0c9c410

8 files changed

+240
-8
lines changed

src/Log4NetTextFormatter.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Globalization;
45
using System.IO;
56
using System.Linq;
67
using System.Text.RegularExpressions;
@@ -93,6 +94,36 @@ public Log4NetTextFormatter(Action<Log4NetTextFormatterOptionsBuilder>? configur
9394
_usesLog4JCompatibility = ReferenceEquals(Log4NetTextFormatterOptionsBuilder.Log4JXmlNamespace, _options.XmlNamespace);
9495
}
9596

97+
private const string NullTextDefaultMarker = "Serilog.Formatting.Log4Net.Log4NetTextFormatter.nullText.default";
98+
99+
/// <summary>
100+
/// Do not use this constructor. It is only available for the Serilog.Settings.Configuration integration.
101+
/// </summary>
102+
[Obsolete("This constructor is only for use by the Serilog.Settings.Configuration package.", error: true)]
103+
[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used by Serilog.Settings.Configuration through reflection.")]
104+
public Log4NetTextFormatter(
105+
string? formatProvider = null,
106+
CDataMode? cDataMode = null,
107+
string? nullText = NullTextDefaultMarker,
108+
bool noXmlNamespace = false,
109+
LineEnding? lineEnding = null,
110+
IndentationSettings? indentationSettings = null,
111+
bool noIndentation = false,
112+
bool log4JCompatibility = false
113+
) : this(options =>
114+
{
115+
if (formatProvider != null) options.UseFormatProvider(CultureInfo.GetCultureInfo(formatProvider));
116+
if (cDataMode != null) options.UseCDataMode(cDataMode.Value);
117+
if (nullText != NullTextDefaultMarker) options.UseNullText(nullText);
118+
if (noXmlNamespace) options.UseNoXmlNamespace();
119+
if (lineEnding != null) options.UseLineEnding(lineEnding.Value);
120+
if (indentationSettings != null) options.UseIndentationSettings(indentationSettings);
121+
if (noIndentation) options.UseNoIndentation();
122+
if (log4JCompatibility) options.UseLog4JCompatibility();
123+
})
124+
{
125+
}
126+
96127
/// <summary>
97128
/// Format the log event as log4net or log4j compatible XML format into the output.
98129
/// </summary>

src/Log4NetTextFormatterOptions.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,26 @@ internal Log4NetTextFormatterOptions(IFormatProvider? formatProvider, CDataMode
2121
}
2222

2323
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseFormatProvider"/></summary>
24-
internal IFormatProvider? FormatProvider { get; }
24+
public IFormatProvider? FormatProvider { get; }
2525

2626
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseCDataMode"/></summary>
27-
internal CDataMode CDataMode { get; }
27+
public CDataMode CDataMode { get; }
2828

2929
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseNullText"/></summary>
30-
internal string? NullText { get; }
30+
public string? NullText { get; }
3131

3232
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseNoXmlNamespace"/></summary>
33-
internal XmlQualifiedName? XmlNamespace { get; }
33+
public XmlQualifiedName? XmlNamespace { get; }
3434

3535
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.CreateXmlWriterSettings"/></summary>
36-
internal XmlWriterSettings XmlWriterSettings { get; }
36+
public XmlWriterSettings XmlWriterSettings { get; }
3737

3838
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UsePropertyFilter"/></summary>
39-
internal PropertyFilter FilterProperty { get; }
39+
public PropertyFilter FilterProperty { get; }
4040

4141
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseMessageFormatter"/></summary>
42-
internal MessageFormatter FormatMessage { get; }
42+
public MessageFormatter FormatMessage { get; }
4343

4444
/// <summary>See <see cref="Log4NetTextFormatterOptionsBuilder.UseExceptionFormatter"/></summary>
45-
internal ExceptionFormatter FormatException { get; }
45+
public ExceptionFormatter FormatException { get; }
4646
}

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.net6.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(".NETCoreApp,Version=v6.0", FrameworkDisplayName=".NET 6.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 = "Serilog.Formatting.Log4Net.Log4NetTextFormatter.nullText.default", bool noXmlNamespace = false, Serilog.Formatting.Log4Net.LineEnding? lineEnding = default, Serilog.Formatting.Log4Net.IndentationSettings? indentationSettings = null, bool noIndentation = false, 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/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 = "Serilog.Formatting.Log4Net.Log4NetTextFormatter.nullText.default", bool noXmlNamespace = false, Serilog.Formatting.Log4Net.LineEnding? lineEnding = default, Serilog.Formatting.Log4Net.IndentationSettings? indentationSettings = null, bool noIndentation = false, 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 = "Serilog.Formatting.Log4Net.Log4NetTextFormatter.nullText.default", bool noXmlNamespace = false, Serilog.Formatting.Log4Net.LineEnding? lineEnding = default, Serilog.Formatting.Log4Net.IndentationSettings? indentationSettings = null, bool noIndentation = false, 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.1.0" />
1010
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="all" />
11+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.9" />
1112
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
13+
<PackageReference Include="NuGet.Versioning" Version="6.14.0" />
1214
<PackageReference Include="PublicApiGenerator" Version="11.4.6" />
15+
<PackageReference Include="ReflectionMagic" Version="5.0.1" />
1316
<PackageReference Include="ReportGenerator" Version="5.4.13" 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="30.17.0" GeneratePathProperty="true" />
1824
<PackageReference Include="Verify.Xunit" Version="30.17.0" />
1925
<PackageReference Include="xunit" Version="2.9.3" />
2026
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" PrivateAssets="all" />
27+
<PackageReference Include="Xunit.SkippableFact" Version="1.5.23" />
2128
</ItemGroup>
2229

2330
<ItemGroup>
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.IO;
5+
using System.Reflection;
6+
using System.Text;
7+
using System.Xml;
8+
using AwesomeAssertions;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.Configuration.Json;
11+
using NuGet.Versioning;
12+
using ReflectionMagic;
13+
using Serilog.Core;
14+
using Serilog.Settings.Configuration;
15+
using Serilog.Sinks.File;
16+
using Tomlyn.Extensions.Configuration;
17+
using Xunit;
18+
19+
namespace Serilog.Formatting.Log4Net.Tests;
20+
21+
public class SerilogSettingsConfigurationTest
22+
{
23+
private static readonly PropertyFilter DefaultPropertyFilter;
24+
private static readonly MessageFormatter DefaultMessageFormatter;
25+
private static readonly ExceptionFormatter DefaultExceptionFormatter;
26+
private static readonly SemanticVersion MicrosoftExtensionsConfigurationVersion;
27+
28+
static SerilogSettingsConfigurationTest()
29+
{
30+
var defaultBuilder = new Log4NetTextFormatterOptionsBuilder().AsDynamic();
31+
32+
DefaultPropertyFilter = defaultBuilder._filterProperty;
33+
DefaultMessageFormatter = defaultBuilder._formatMessage;
34+
DefaultExceptionFormatter = defaultBuilder._formatException;
35+
36+
var assembly = typeof(JsonStreamConfigurationSource).Assembly;
37+
var informationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
38+
MicrosoftExtensionsConfigurationVersion = SemanticVersion.Parse(informationalVersion ?? throw new InvalidDataException($"The [AssemblyInformationalVersion] attribute is missing from {assembly}"));
39+
}
40+
41+
42+
[Fact]
43+
public void ConfigureDefault()
44+
{
45+
// lang=toml
46+
var config =
47+
"""
48+
[[Serilog.WriteTo]]
49+
Name = 'File'
50+
Args.path = 'logs.xml'
51+
Args.formatter.type = 'Serilog.Formatting.Log4Net.Log4NetTextFormatter, Serilog.Formatting.Log4Net'
52+
""";
53+
54+
var options = GetLog4NetTextFormatterOptions(config);
55+
56+
options.Should().BeEquivalentTo(new Log4NetTextFormatterOptions(
57+
formatProvider: null,
58+
cDataMode: CDataMode.Always,
59+
nullText: "(null)",
60+
xmlNamespace: new XmlQualifiedName("log4net", "http://logging.apache.org/log4net/schemas/log4net-events-1.2/"),
61+
xmlWriterSettings: new XmlWriterSettings { Indent = true, IndentChars = " ", ConformanceLevel = ConformanceLevel.Fragment },
62+
filterProperty: DefaultPropertyFilter,
63+
formatMessage: DefaultMessageFormatter,
64+
formatException: DefaultExceptionFormatter
65+
));
66+
}
67+
68+
[Theory]
69+
[InlineData(CDataMode.Always)]
70+
[InlineData(CDataMode.Never)]
71+
[InlineData(CDataMode.IfNeeded)]
72+
public void ConfigureCDataMode(CDataMode cDataMode)
73+
{
74+
// lang=toml
75+
var config =
76+
$"""
77+
[[Serilog.WriteTo]]
78+
Name = 'File'
79+
Args.path = 'logs.xml'
80+
Args.formatter.type = 'Serilog.Formatting.Log4Net.Log4NetTextFormatter, Serilog.Formatting.Log4Net'
81+
Args.formatter.cDataMode = '{cDataMode}'
82+
""";
83+
84+
var options = GetLog4NetTextFormatterOptions(config);
85+
86+
options.Should().BeEquivalentTo(new Log4NetTextFormatterOptions(
87+
formatProvider: null,
88+
cDataMode: cDataMode,
89+
nullText: "(null)",
90+
xmlNamespace: new XmlQualifiedName("log4net", "http://logging.apache.org/log4net/schemas/log4net-events-1.2/"),
91+
xmlWriterSettings: new XmlWriterSettings { Indent = true, IndentChars = " ", ConformanceLevel = ConformanceLevel.Fragment },
92+
filterProperty: DefaultPropertyFilter,
93+
formatMessage: DefaultMessageFormatter,
94+
formatException: DefaultExceptionFormatter
95+
));
96+
}
97+
98+
[Theory]
99+
[InlineData("iv")]
100+
[InlineData("fr-CH")]
101+
public void ConfigureFormatProvider(string formatProvider)
102+
{
103+
// lang=toml
104+
var config =
105+
$"""
106+
[[Serilog.WriteTo]]
107+
Name = 'File'
108+
Args.path = 'logs.xml'
109+
Args.formatter.type = 'Serilog.Formatting.Log4Net.Log4NetTextFormatter, Serilog.Formatting.Log4Net'
110+
Args.formatter.formatProvider = '{formatProvider}'
111+
""";
112+
113+
var options = GetLog4NetTextFormatterOptions(config);
114+
115+
options.FormatProvider.Should().BeSameAs(CultureInfo.GetCultureInfo(formatProvider));
116+
}
117+
118+
[SkippableTheory]
119+
[InlineData(null)]
120+
[InlineData("")]
121+
[InlineData("<null>")]
122+
[InlineData("🌀")]
123+
public void ConfigureNullText(string? nullText)
124+
{
125+
// Null values preserved in configuration was introduced in .NET 10 Preview 7
126+
// See https://learn.microsoft.com/en-us/dotnet/core/compatibility/extensions/10.0/configuration-null-values-preserved
127+
Skip.If(nullText == null && MicrosoftExtensionsConfigurationVersion.Major < 10, "null values are preserved in configuration since .NET 10 Preview 7");
128+
129+
// lang=json
130+
var config =
131+
$$"""
132+
{
133+
"Serilog": {
134+
"WriteTo:File": {
135+
"Name": "File",
136+
"Args": {
137+
"path": "logs.xml",
138+
"formatter": {
139+
"type": "Serilog.Formatting.Log4Net.Log4NetTextFormatter, Serilog.Formatting.Log4Net",
140+
"nullText": {{(nullText == null ? "null" : $"\"{nullText}\"")}}
141+
}
142+
}
143+
}
144+
}
145+
}
146+
""";
147+
var options = GetLog4NetTextFormatterOptions(config);
148+
149+
options.NullText.Should().Be(nullText);
150+
}
151+
152+
[Fact]
153+
public void ConfigureNoXmlNamespace()
154+
{
155+
// lang=toml
156+
const string config =
157+
"""
158+
[[Serilog.WriteTo]]
159+
Name = 'File'
160+
Args.path = 'logs.xml'
161+
Args.formatter.type = 'Serilog.Formatting.Log4Net.Log4NetTextFormatter, Serilog.Formatting.Log4Net'
162+
Args.formatter.noXmlNamespace = true
163+
""";
164+
165+
var options = GetLog4NetTextFormatterOptions(config);
166+
167+
options.XmlNamespace.Should().BeNull();
168+
}
169+
170+
private static Log4NetTextFormatterOptions GetLog4NetTextFormatterOptions(string config)
171+
{
172+
using var configStream = new MemoryStream(Encoding.UTF8.GetBytes(config));
173+
var configBuilder = new ConfigurationBuilder();
174+
var configuration = (config.StartsWith('{') ? configBuilder.AddJsonStream(configStream) : configBuilder.AddTomlStream(configStream)).Build();
175+
var options = new ConfigurationReaderOptions(typeof(ILogger).Assembly, typeof(FileSink).Assembly, typeof(Log4NetTextFormatter).Assembly);
176+
using var logger = new LoggerConfiguration().ReadFrom.Configuration(configuration, options).CreateLogger();
177+
IEnumerable<ILogEventSink> sinks = logger.AsDynamic()._sink._sinks;
178+
var fileSink = sinks.Should().ContainSingle().Which.Should().BeOfType<FileSink>().Subject;
179+
return fileSink.AsDynamic()._textFormatter._options;
180+
}
181+
}

0 commit comments

Comments
 (0)