1212using Serilog . Debugging ;
1313using Serilog . Events ;
1414using System . Linq . Expressions ;
15+ using System . Text . RegularExpressions ;
1516
1617namespace Serilog . Settings . Configuration
1718{
1819 class ConfigurationReader : IConfigurationReader
1920 {
21+ const string LevelSwitchNameRegex = @"^\$[A-Za-z]+[A-Za-z0-9]*$" ;
22+
2023 readonly IConfigurationSection _configuration ;
2124 readonly DependencyContext _dependencyContext ;
2225 readonly Assembly [ ] _configurationAssemblies ;
@@ -37,14 +40,48 @@ public ConfigurationReader(IConfigurationSection configuration, DependencyContex
3740
3841 public void Configure ( LoggerConfiguration loggerConfiguration )
3942 {
40- ApplyMinimumLevel ( loggerConfiguration ) ;
41- ApplyEnrichment ( loggerConfiguration ) ;
42- ApplyFilters ( loggerConfiguration ) ;
43- ApplySinks ( loggerConfiguration ) ;
44- ApplyAuditSinks ( loggerConfiguration ) ;
45- }
46-
47- void ApplyMinimumLevel ( LoggerConfiguration loggerConfiguration )
43+ var declaredLevelSwitches = ProcessLevelSwitchDeclarations ( ) ;
44+ ApplyMinimumLevel ( loggerConfiguration , declaredLevelSwitches ) ;
45+ ApplyEnrichment ( loggerConfiguration , declaredLevelSwitches ) ;
46+ ApplyFilters ( loggerConfiguration , declaredLevelSwitches ) ;
47+ ApplySinks ( loggerConfiguration , declaredLevelSwitches ) ;
48+ ApplyAuditSinks ( loggerConfiguration , declaredLevelSwitches ) ;
49+ }
50+
51+ private IReadOnlyDictionary < string , LoggingLevelSwitch > ProcessLevelSwitchDeclarations ( )
52+ {
53+ var levelSwitchesDirective = _configuration . GetSection ( "LevelSwitches" ) ;
54+ var namedSwitches = new Dictionary < string , LoggingLevelSwitch > ( ) ;
55+ if ( levelSwitchesDirective != null )
56+ {
57+ foreach ( var levelSwitchDeclaration in levelSwitchesDirective . GetChildren ( ) )
58+ {
59+ var switchName = levelSwitchDeclaration . Key ;
60+ var switchInitialLevel = levelSwitchDeclaration . Value ;
61+ // switchName must be something like $switch to avoid ambiguities
62+ if ( ! IsValidSwitchName ( switchName ) )
63+ {
64+ throw new FormatException ( $ "\" { switchName } \" is not a valid name for a Level Switch declaration. Level switch must be declared with a '$' sign, like \" LevelSwitches\" : {{\" $switchName\" : \" InitialLevel\" }}") ;
65+ }
66+ LoggingLevelSwitch newSwitch ;
67+ if ( string . IsNullOrEmpty ( switchInitialLevel ) )
68+ {
69+ newSwitch = new LoggingLevelSwitch ( ) ;
70+ }
71+ else
72+ {
73+ var initialLevel = ParseLogEventLevel ( switchInitialLevel ) ;
74+ newSwitch = new LoggingLevelSwitch ( initialLevel ) ;
75+ }
76+ namedSwitches . Add ( switchName , newSwitch ) ;
77+ }
78+ }
79+
80+ return namedSwitches ;
81+ }
82+
83+ void ApplyMinimumLevel ( LoggerConfiguration loggerConfiguration ,
84+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
4885 {
4986 var minimumLevelDirective = _configuration . GetSection ( "MinimumLevel" ) ;
5087
@@ -54,15 +91,33 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
5491 ApplyMinimumLevel ( defaultMinLevelDirective , ( configuration , levelSwitch ) => configuration . ControlledBy ( levelSwitch ) ) ;
5592 }
5693
94+ var minLevelControlledByDirective = minimumLevelDirective . GetSection ( "ControlledBy" ) ;
95+ if ( minLevelControlledByDirective ? . Value != null )
96+ {
97+ var globalMinimumLevelSwitch = declaredLevelSwitches . LookUpSwitchByName ( minLevelControlledByDirective . Value ) ;
98+ // not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
99+ loggerConfiguration . MinimumLevel . ControlledBy ( globalMinimumLevelSwitch ) ;
100+ }
101+
57102 foreach ( var overrideDirective in minimumLevelDirective . GetSection ( "Override" ) . GetChildren ( ) )
58103 {
59- ApplyMinimumLevel ( overrideDirective , ( configuration , levelSwitch ) => configuration . Override ( overrideDirective . Key , levelSwitch ) ) ;
104+ var overridePrefix = overrideDirective . Key ;
105+ var overridenLevelOrSwitch = overrideDirective . Value ;
106+ if ( Enum . TryParse ( overridenLevelOrSwitch , out LogEventLevel _ ) )
107+ {
108+ ApplyMinimumLevel ( overrideDirective , ( configuration , levelSwitch ) => configuration . Override ( overridePrefix , levelSwitch ) ) ;
109+ }
110+ else
111+ {
112+ var overrideSwitch = declaredLevelSwitches . LookUpSwitchByName ( overridenLevelOrSwitch ) ;
113+ // not calling ApplyMinimumLevel local function because here we have a reference to a LogLevelSwitch already
114+ loggerConfiguration . MinimumLevel . Override ( overridePrefix , overrideSwitch ) ;
115+ }
60116 }
61117
62118 void ApplyMinimumLevel ( IConfigurationSection directive , Action < LoggerMinimumLevelConfiguration , LoggingLevelSwitch > applyConfigAction )
63119 {
64- if ( ! Enum . TryParse ( directive . Value , out LogEventLevel minimumLevel ) )
65- throw new InvalidOperationException ( $ "The value { directive . Value } is not a valid Serilog level.") ;
120+ var minimumLevel = ParseLogEventLevel ( directive . Value ) ;
66121
67122 var levelSwitch = new LoggingLevelSwitch ( minimumLevel ) ;
68123 applyConfigAction ( loggerConfiguration . MinimumLevel , levelSwitch ) ;
@@ -79,49 +134,56 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action<LoggerMinimumLeve
79134 }
80135 }
81136
82- void ApplyFilters ( LoggerConfiguration loggerConfiguration )
137+
138+
139+ void ApplyFilters ( LoggerConfiguration loggerConfiguration ,
140+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
83141 {
84142 var filterDirective = _configuration . GetSection ( "Filter" ) ;
85143 if ( filterDirective != null )
86144 {
87145 var methodCalls = GetMethodCalls ( filterDirective ) ;
88- CallConfigurationMethods ( methodCalls , FindFilterConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Filter ) ;
146+ CallConfigurationMethods ( methodCalls , FindFilterConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Filter , declaredLevelSwitches ) ;
89147 }
90148 }
91149
92- void ApplySinks ( LoggerConfiguration loggerConfiguration )
150+ void ApplySinks ( LoggerConfiguration loggerConfiguration ,
151+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
93152 {
94153 var writeToDirective = _configuration . GetSection ( "WriteTo" ) ;
95154 if ( writeToDirective != null )
96155 {
97156 var methodCalls = GetMethodCalls ( writeToDirective ) ;
98- CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . WriteTo ) ;
157+ CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . WriteTo , declaredLevelSwitches ) ;
99158 }
100159 }
101160
102- void ApplyAuditSinks ( LoggerConfiguration loggerConfiguration )
161+ void ApplyAuditSinks ( LoggerConfiguration loggerConfiguration ,
162+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
103163 {
104164 var auditToDirective = _configuration . GetSection ( "AuditTo" ) ;
105165 if ( auditToDirective != null )
106166 {
107167 var methodCalls = GetMethodCalls ( auditToDirective ) ;
108- CallConfigurationMethods ( methodCalls , FindAuditSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . AuditTo ) ;
168+ CallConfigurationMethods ( methodCalls , FindAuditSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . AuditTo , declaredLevelSwitches ) ;
109169 }
110170 }
111171
112- void IConfigurationReader . ApplySinks ( LoggerSinkConfiguration loggerSinkConfiguration )
172+ void IConfigurationReader . ApplySinks ( LoggerSinkConfiguration loggerSinkConfiguration ,
173+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
113174 {
114175 var methodCalls = GetMethodCalls ( _configuration ) ;
115- CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerSinkConfiguration ) ;
176+ CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerSinkConfiguration , declaredLevelSwitches ) ;
116177 }
117178
118- void ApplyEnrichment ( LoggerConfiguration loggerConfiguration )
179+ void ApplyEnrichment ( LoggerConfiguration loggerConfiguration ,
180+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
119181 {
120182 var enrichDirective = _configuration . GetSection ( "Enrich" ) ;
121183 if ( enrichDirective != null )
122184 {
123185 var methodCalls = GetMethodCalls ( enrichDirective ) ;
124- CallConfigurationMethods ( methodCalls , FindEventEnricherConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Enrich ) ;
186+ CallConfigurationMethods ( methodCalls , FindEventEnricherConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Enrich , declaredLevelSwitches ) ;
125187 }
126188
127189 var propertiesDirective = _configuration . GetSection ( "Properties" ) ;
@@ -234,7 +296,7 @@ where filter(assemblyFileName)
234296 return query . ToArray ( ) ;
235297 }
236298
237- static void CallConfigurationMethods ( ILookup < string , Dictionary < string , IConfigurationArgumentValue > > methods , IList < MethodInfo > configurationMethods , object receiver )
299+ static void CallConfigurationMethods ( ILookup < string , Dictionary < string , IConfigurationArgumentValue > > methods , IList < MethodInfo > configurationMethods , object receiver , IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
238300 {
239301 foreach ( var method in methods . SelectMany ( g => g . Select ( x => new { g . Key , Value = x } ) ) )
240302 {
@@ -244,7 +306,7 @@ static void CallConfigurationMethods(ILookup<string, Dictionary<string, IConfigu
244306 {
245307 var call = ( from p in methodInfo . GetParameters ( ) . Skip ( 1 )
246308 let directive = method . Value . FirstOrDefault ( s => s . Key == p . Name )
247- select directive . Key == null ? p . DefaultValue : directive . Value . ConvertTo ( p . ParameterType ) ) . ToList ( ) ;
309+ select directive . Key == null ? p . DefaultValue : directive . Value . ConvertTo ( p . ParameterType , declaredLevelSwitches ) ) . ToList ( ) ;
248310
249311 call . Insert ( 0 , receiver ) ;
250312
@@ -322,5 +384,17 @@ internal static LoggerConfiguration Logger(LoggerSinkConfiguration loggerSinkCon
322384
323385 internal static MethodInfo GetSurrogateConfigurationMethod < TConfiguration , TArg1 , TArg2 > ( Expression < Action < TConfiguration , TArg1 , TArg2 > > method )
324386 => ( method . Body as MethodCallExpression ) ? . Method ;
387+
388+ internal static bool IsValidSwitchName ( string input )
389+ {
390+ return Regex . IsMatch ( input , LevelSwitchNameRegex ) ;
391+ }
392+
393+ internal static LogEventLevel ParseLogEventLevel ( string value )
394+ {
395+ if ( ! Enum . TryParse ( value , out LogEventLevel parsedLevel ) )
396+ throw new InvalidOperationException ( $ "The value { value } is not a valid Serilog level.") ;
397+ return parsedLevel ;
398+ }
325399 }
326400}
0 commit comments