12
12
using Serilog . Debugging ;
13
13
using Serilog . Events ;
14
14
using System . Linq . Expressions ;
15
+ using System . Text . RegularExpressions ;
15
16
16
17
namespace Serilog . Settings . Configuration
17
18
{
18
19
class ConfigurationReader : IConfigurationReader
19
20
{
21
+ const string LevelSwitchNameRegex = @"^\$[A-Za-z]+[A-Za-z0-9]*$" ;
22
+
20
23
readonly IConfigurationSection _configuration ;
21
24
readonly DependencyContext _dependencyContext ;
22
25
readonly Assembly [ ] _configurationAssemblies ;
@@ -37,14 +40,48 @@ public ConfigurationReader(IConfigurationSection configuration, DependencyContex
37
40
38
41
public void Configure ( LoggerConfiguration loggerConfiguration )
39
42
{
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 )
48
85
{
49
86
var minimumLevelDirective = _configuration . GetSection ( "MinimumLevel" ) ;
50
87
@@ -54,15 +91,33 @@ void ApplyMinimumLevel(LoggerConfiguration loggerConfiguration)
54
91
ApplyMinimumLevel ( defaultMinLevelDirective , ( configuration , levelSwitch ) => configuration . ControlledBy ( levelSwitch ) ) ;
55
92
}
56
93
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
+
57
102
foreach ( var overrideDirective in minimumLevelDirective . GetSection ( "Override" ) . GetChildren ( ) )
58
103
{
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
+ }
60
116
}
61
117
62
118
void ApplyMinimumLevel ( IConfigurationSection directive , Action < LoggerMinimumLevelConfiguration , LoggingLevelSwitch > applyConfigAction )
63
119
{
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 ) ;
66
121
67
122
var levelSwitch = new LoggingLevelSwitch ( minimumLevel ) ;
68
123
applyConfigAction ( loggerConfiguration . MinimumLevel , levelSwitch ) ;
@@ -79,49 +134,56 @@ void ApplyMinimumLevel(IConfigurationSection directive, Action<LoggerMinimumLeve
79
134
}
80
135
}
81
136
82
- void ApplyFilters ( LoggerConfiguration loggerConfiguration )
137
+
138
+
139
+ void ApplyFilters ( LoggerConfiguration loggerConfiguration ,
140
+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
83
141
{
84
142
var filterDirective = _configuration . GetSection ( "Filter" ) ;
85
143
if ( filterDirective != null )
86
144
{
87
145
var methodCalls = GetMethodCalls ( filterDirective ) ;
88
- CallConfigurationMethods ( methodCalls , FindFilterConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Filter ) ;
146
+ CallConfigurationMethods ( methodCalls , FindFilterConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Filter , declaredLevelSwitches ) ;
89
147
}
90
148
}
91
149
92
- void ApplySinks ( LoggerConfiguration loggerConfiguration )
150
+ void ApplySinks ( LoggerConfiguration loggerConfiguration ,
151
+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
93
152
{
94
153
var writeToDirective = _configuration . GetSection ( "WriteTo" ) ;
95
154
if ( writeToDirective != null )
96
155
{
97
156
var methodCalls = GetMethodCalls ( writeToDirective ) ;
98
- CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . WriteTo ) ;
157
+ CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . WriteTo , declaredLevelSwitches ) ;
99
158
}
100
159
}
101
160
102
- void ApplyAuditSinks ( LoggerConfiguration loggerConfiguration )
161
+ void ApplyAuditSinks ( LoggerConfiguration loggerConfiguration ,
162
+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
103
163
{
104
164
var auditToDirective = _configuration . GetSection ( "AuditTo" ) ;
105
165
if ( auditToDirective != null )
106
166
{
107
167
var methodCalls = GetMethodCalls ( auditToDirective ) ;
108
- CallConfigurationMethods ( methodCalls , FindAuditSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . AuditTo ) ;
168
+ CallConfigurationMethods ( methodCalls , FindAuditSinkConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . AuditTo , declaredLevelSwitches ) ;
109
169
}
110
170
}
111
171
112
- void IConfigurationReader . ApplySinks ( LoggerSinkConfiguration loggerSinkConfiguration )
172
+ void IConfigurationReader . ApplySinks ( LoggerSinkConfiguration loggerSinkConfiguration ,
173
+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
113
174
{
114
175
var methodCalls = GetMethodCalls ( _configuration ) ;
115
- CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerSinkConfiguration ) ;
176
+ CallConfigurationMethods ( methodCalls , FindSinkConfigurationMethods ( _configurationAssemblies ) , loggerSinkConfiguration , declaredLevelSwitches ) ;
116
177
}
117
178
118
- void ApplyEnrichment ( LoggerConfiguration loggerConfiguration )
179
+ void ApplyEnrichment ( LoggerConfiguration loggerConfiguration ,
180
+ IReadOnlyDictionary < string , LoggingLevelSwitch > declaredLevelSwitches )
119
181
{
120
182
var enrichDirective = _configuration . GetSection ( "Enrich" ) ;
121
183
if ( enrichDirective != null )
122
184
{
123
185
var methodCalls = GetMethodCalls ( enrichDirective ) ;
124
- CallConfigurationMethods ( methodCalls , FindEventEnricherConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Enrich ) ;
186
+ CallConfigurationMethods ( methodCalls , FindEventEnricherConfigurationMethods ( _configurationAssemblies ) , loggerConfiguration . Enrich , declaredLevelSwitches ) ;
125
187
}
126
188
127
189
var propertiesDirective = _configuration . GetSection ( "Properties" ) ;
@@ -234,7 +296,7 @@ where filter(assemblyFileName)
234
296
return query . ToArray ( ) ;
235
297
}
236
298
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 )
238
300
{
239
301
foreach ( var method in methods . SelectMany ( g => g . Select ( x => new { g . Key , Value = x } ) ) )
240
302
{
@@ -244,7 +306,7 @@ static void CallConfigurationMethods(ILookup<string, Dictionary<string, IConfigu
244
306
{
245
307
var call = ( from p in methodInfo . GetParameters ( ) . Skip ( 1 )
246
308
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 ( ) ;
248
310
249
311
call . Insert ( 0 , receiver ) ;
250
312
@@ -322,5 +384,17 @@ internal static LoggerConfiguration Logger(LoggerSinkConfiguration loggerSinkCon
322
384
323
385
internal static MethodInfo GetSurrogateConfigurationMethod < TConfiguration , TArg1 , TArg2 > ( Expression < Action < TConfiguration , TArg1 , TArg2 > > method )
324
386
=> ( 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
+ }
325
399
}
326
400
}
0 commit comments