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