-
Notifications
You must be signed in to change notification settings - Fork 499
Expand file tree
/
Copy pathLambdaLoggerOptions.cs
More file actions
261 lines (232 loc) · 11 KB
/
LambdaLoggerOptions.cs
File metadata and controls
261 lines (232 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Options that can be used to configure Lambda logging.
/// </summary>
public class LambdaLoggerOptions
{
// Default configuration section.
// Customer configuration will be fetched from this section, unless otherwise specified.
internal const string DEFAULT_SECTION_NAME = "Lambda.Logging";
private const string INCLUDE_LOG_LEVEL_KEY = "IncludeLogLevel";
private const string INCLUDE_CATEGORY_KEY = "IncludeCategory";
private const string INCLUDE_NEWLINE_KEY = "IncludeNewline";
private const string INCLUDE_EXCEPTION_KEY = "IncludeException";
private const string INCLUDE_EVENT_ID_KEY = "IncludeEventId";
private const string INCLUDE_SCOPES_KEY = "IncludeScopes";
private const string LOG_LEVEL_KEY = "LogLevel";
private const string DEFAULT_CATEGORY = "Default";
/// <summary>
/// Flag to indicate if LogLevel should be part of logged message.
/// Default is true.
/// </summary>
public bool IncludeLogLevel { get; set; }
/// <summary>
/// Flag to indicate if Category should be part of logged message.
/// Default is true.
/// </summary>
public bool IncludeCategory { get; set; }
/// <summary>
/// Flag to indicate if logged messages should have a newline appended
/// to them, if one isn't already there.
/// Default is true.
/// </summary>
public bool IncludeNewline { get; set; }
/// <summary>
/// Flag to indicate if Exception should be part of logged message.
/// Default is false.
/// </summary>
public bool IncludeException { get; set; }
/// <summary>
/// Flag to indicate if EventId should be part of logged message.
/// Default is false.
/// </summary>
public bool IncludeEventId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether scopes should be included in the message.
/// Defaults to <c>false</c>.
/// </summary>
public bool IncludeScopes { get; set; }
/// <summary>
/// Function used to filter events based on the log level.
/// Default value is null and will instruct logger to log everything.
/// </summary>
[CLSCompliant(false)] // https://github.com/aspnet/Logging/issues/500
public Func<string, LogLevel, bool> Filter { get; set; }
/// <summary>
/// Constructs instance of LambdaLoggerOptions with default values.
/// </summary>
public LambdaLoggerOptions()
{
IncludeCategory = true;
IncludeLogLevel = true;
IncludeNewline = true;
IncludeException = false;
IncludeEventId = false;
IncludeScopes = false;
Filter = null;
}
/// <summary>
/// Constructs instance of LambdaLoggerOptions with values from "Lambda.Logging"
/// subsection of the specified configuration.
/// The following configuration keys are supported:
/// IncludeLogLevel - boolean flag indicates if LogLevel should be part of logged message.
/// IncludeCategory - boolean flag indicates if Category should be part of logged message.
/// LogLevels - category-to-LogLevel mapping which indicates minimum LogLevel for a category.
/// </summary>
/// <param name="configuration"></param>
[CLSCompliant(false)] // https://github.com/aspnet/Logging/issues/500
public LambdaLoggerOptions(IConfiguration configuration)
: this(configuration, DEFAULT_SECTION_NAME)
{ }
/// <summary>
/// Constructs instance of LambdaLoggerOptions with values from specified
/// subsection of the configuration.
/// The following configuration keys are supported:
/// IncludeLogLevel - boolean flag indicates if LogLevel should be part of logged message.
/// IncludeCategory - boolean flag indicates if Category should be part of logged message.
/// LogLevels - category-to-LogLevel mapping which indicates minimum LogLevel for a category.
/// </summary>
/// <param name="configuration"></param>
/// <param name="loggingSectionName"></param>
[CLSCompliant(false)] // https://github.com/aspnet/Logging/issues/500
public LambdaLoggerOptions(IConfiguration configuration, string loggingSectionName)
: this()
{
// Input validation
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (string.IsNullOrEmpty(loggingSectionName))
{
throw new ArgumentNullException(nameof(loggingSectionName));
}
var loggerConfiguration = configuration.GetSection(loggingSectionName);
if (loggerConfiguration == null)
{
throw new ArgumentOutOfRangeException(nameof(loggingSectionName), $"Unable to find section '{loggingSectionName}' in current configuration.");
}
// Parse settings
if (TryGetString(loggerConfiguration, INCLUDE_CATEGORY_KEY, out string includeCategoryString))
{
IncludeCategory = bool.Parse(includeCategoryString);
}
if (TryGetString(loggerConfiguration, INCLUDE_LOG_LEVEL_KEY, out string includeLogLevelString))
{
IncludeLogLevel = bool.Parse(includeLogLevelString);
}
if (TryGetString(loggerConfiguration, INCLUDE_EXCEPTION_KEY, out string includeExceptionString))
{
IncludeException = bool.Parse(includeExceptionString);
}
if (TryGetString(loggerConfiguration, INCLUDE_EVENT_ID_KEY, out string includeEventIdString))
{
IncludeEventId = bool.Parse(includeEventIdString);
}
if (TryGetString(loggerConfiguration, INCLUDE_NEWLINE_KEY, out string includeNewlineString))
{
IncludeNewline = bool.Parse(includeNewlineString);
}
if (TryGetSection(loggerConfiguration, LOG_LEVEL_KEY, out IConfiguration logLevelsSection))
{
Filter = CreateFilter(logLevelsSection);
}
if (TryGetString(loggerConfiguration, INCLUDE_SCOPES_KEY, out string includeScopesString))
{
IncludeScopes = bool.Parse(includeScopesString);
}
}
// Retrieves configuration value for key, if one exists
private static bool TryGetString(IConfiguration configuration, string key, out string value)
{
value = configuration[key];
return (value != null);
}
// Retrieves configuration section for key, if one exists
private static bool TryGetSection(IConfiguration configuration, string key, out IConfiguration value)
{
value = configuration.GetSection(key);
return (value != null);
}
// Creates filter for log levels section
private static Func<string, LogLevel, bool> CreateFilter(IConfiguration logLevelsSection)
{
// Empty section means log everything
var logLevels = logLevelsSection.GetChildren().ToList();
if (logLevels.Count == 0)
{
return null;
}
// Populate mapping of category to LogLevel
var logLevelsMapping = new Dictionary<string, LogLevel>(StringComparer.Ordinal);
var defaultLogLevel = LogLevel.Information;
foreach (var logLevel in logLevels)
{
var category = logLevel.Key;
var minLevelValue = logLevel.Value;
LogLevel minLevel;
if (!Enum.TryParse(minLevelValue, out minLevel))
{
throw new InvalidCastException($"Unable to convert level '{minLevelValue}' for category '{category}' to LogLevel.");
}
if (category.Contains("*"))
{
// Only 1 wildcard is supported
var wildcardCount = category.Count(x => x == '*');
if (wildcardCount > 1)
{
throw new ArgumentOutOfRangeException($"Category '{category}' is invalid - only 1 wildcard is supported in a category.");
}
// Wildcards are only supported at the end of a Category name!
var wildcardLocation = category.IndexOf('*');
if (wildcardLocation != category.Length - 1)
{
throw new ArgumentException($"Category '{category}' is invalid - wilcards are only supported at the end of a category.");
}
var trimmedCategory = category.TrimEnd('*');
logLevelsMapping[trimmedCategory] = minLevel;
}
else if (category.Equals(DEFAULT_CATEGORY, StringComparison.OrdinalIgnoreCase))
{
defaultLogLevel = minLevel;
}
else
{
logLevelsMapping[category] = minLevel;
}
}
// Extract a reverse sorted list of categories. This allows us to quickly search for most specific match to least specific.
var orderedCategories = logLevelsMapping.Keys
.OrderByDescending(categoryKey => categoryKey)
.ToList();
// Filter lambda that examines mapping
return (string category, LogLevel logLevel) =>
{
LogLevel minLevel;
// Exact match takes priority
if (logLevelsMapping.TryGetValue(category, out minLevel))
{
return logLevel >= minLevel;
}
// Find the most specific wildcard or namespace prefix category that matches the logger category
var matchedCategory = orderedCategories.FirstOrDefault(category.StartsWith);
if (matchedCategory != null)
{
// If no log filters then default to logging the log message.
minLevel = logLevelsMapping[matchedCategory];
return logLevel >= minLevel;
}
else
{
// If no log filters then default to logging the log message.
return (logLevel >= defaultLogLevel);
}
};
}
}
}