-
Notifications
You must be signed in to change notification settings - Fork 120
/
Log4NetProvider.cs
387 lines (341 loc) · 11.5 KB
/
Log4NetProvider.cs
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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
namespace Microsoft.Extensions.Logging
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using log4net;
using log4net.Config;
using log4net.Repository;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Entities;
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Extensions;
using Microsoft.Extensions.Logging.Scope;
/// <summary>
/// The log4net provider class.
/// </summary>
/// <seealso cref="ILoggerProvider" />
public class Log4NetProvider : ILoggerProvider
{
/// <summary>
/// The log4net repository.
/// </summary>
private readonly ILoggerRepository loggerRepository;
/// <summary>
/// The loggers collection.
/// </summary>
private readonly ConcurrentDictionary<string, Log4NetLogger> loggers = new ConcurrentDictionary<string, Log4NetLogger>();
/// <summary>
/// The provider options.
/// </summary>
private readonly Log4NetProviderOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
/// </summary>
public Log4NetProvider()
: this(new Log4NetProviderOptions())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
/// </summary>
/// <param name="log4NetConfigFileName">The log4NetConfigFile.</param>
public Log4NetProvider(string log4NetConfigFileName)
: this(new Log4NetProviderOptions(log4NetConfigFileName))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <exception cref="ArgumentNullException">options</exception>
/// <exception cref="NotSupportedException">Wach cannot be true when you are overwriting config file values with values from configuration section.</exception>
public Log4NetProvider(Log4NetProviderOptions options)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
if (options.Watch
&& options.PropertyOverrides.Any())
{
throw new NotSupportedException("Wach cannot be true when you are overwriting config file values with values from configuration section.");
}
Assembly assembly = null;
if (string.IsNullOrEmpty(this.options.LoggerRepository))
{
#if NETCOREAPP1_1
assembly = Assembly.GetEntryAssembly();
#else
assembly = Assembly.GetExecutingAssembly();
#endif
this.loggerRepository = LogManager.CreateRepository(
assembly ?? GetCallingAssemblyFromStartup(),
typeof(log4net.Repository.Hierarchy.Hierarchy));
}
else
{
this.loggerRepository = LogManager.GetRepository(options.LoggerRepository) ?? LogManager.CreateRepository(
options.LoggerRepository,
typeof(log4net.Repository.Hierarchy.Hierarchy));
}
if (options.ExternalConfigurationSetup)
{
return;
}
string fileNamePath = options.Log4NetConfigFileName;
if (!Path.IsPathRooted(fileNamePath))
{
#if NETCOREAPP1_1
if (!File.Exists(fileNamePath))
{
fileNamePath = Path.Combine(Path.GetDirectoryName(assembly.Location), fileNamePath);
}
#else
fileNamePath = Path.Combine(AppContext.BaseDirectory, fileNamePath);
#endif
}
fileNamePath = Path.GetFullPath(fileNamePath);
if (options.Watch)
{
XmlConfigurator.ConfigureAndWatch(
this.loggerRepository,
new FileInfo(fileNamePath));
}
else
{
var configXml = ParseLog4NetConfigFile(fileNamePath);
if (this.options.PropertyOverrides != null
&& this.options.PropertyOverrides.Any())
{
configXml = UpdateNodesWithOverridingValues(
configXml,
this.options.PropertyOverrides);
}
XmlConfigurator.Configure(this.loggerRepository, configXml.DocumentElement);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
/// </summary>
/// <param name="log4NetConfigFile">The log4 net configuration file.</param>
/// <param name="configurationSection">The configuration section.</param>
[Obsolete("Use Log4NetProvider(Log4NetProviderOptions) instead.")]
public Log4NetProvider(string log4NetConfigFile, IConfigurationSection configurationSection)
: this(log4NetConfigFile, false, configurationSection)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
/// </summary>
/// <param name="log4NetConfigFile">The log4 net configuration file.</param>
/// <param name="watch">if set to <c>true</c> [watch].</param>
[Obsolete("Use Log4NetProvider(Log4NetProviderOptions) instead.")]
public Log4NetProvider(string log4NetConfigFile, bool watch)
: this(log4NetConfigFile, watch, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Log4NetProvider" /> class.
/// </summary>
/// <param name="log4NetConfigFile">The log4 net configuration file.</param>
/// <param name="watch">if set to <c>true</c> [watch].</param>
/// <param name="configurationSection">The configuration section.</param>
/// <exception cref="NotSupportedException">Watch cannot be true if you are overwriting config file values with values from configuration section.</exception>
private Log4NetProvider(string log4NetConfigFile, bool watch, IConfigurationSection configurationSection)
{
if (watch && configurationSection != null)
{
throw new NotSupportedException("Wach cannot be true if you are overwriting config file values with values from configuration section.");
}
#if NETCOREAPP1_1
Assembly assembly = Assembly.GetEntryAssembly();
#else
Assembly assembly = Assembly.GetExecutingAssembly();
#endif
this.loggerRepository = LogManager.CreateRepository(
assembly ?? GetCallingAssemblyFromStartup(),
typeof(log4net.Repository.Hierarchy.Hierarchy));
if (watch)
{
XmlConfigurator.ConfigureAndWatch(this.loggerRepository, new FileInfo(Path.GetFullPath(log4NetConfigFile)));
}
else
{
var configXml = ParseLog4NetConfigFile(log4NetConfigFile);
if (configurationSection != null)
{
configXml = UpdateNodesWithAdditionalConfiguration(configXml, configurationSection);
}
XmlConfigurator.Configure(this.loggerRepository, configXml.DocumentElement);
}
}
/// <summary>
/// Creates the logger.
/// </summary>
/// <returns>An instance of the <see cref="ILogger"/>.</returns>
public ILogger CreateLogger()
=> this.CreateLogger(this.options.Name);
/// <summary>
/// Creates the logger.
/// </summary>
/// <param name="categoryName">The category name.</param>
/// <returns>An instance of the <see cref="ILogger"/>.</returns>
public ILogger CreateLogger(string categoryName)
=> this.loggers.GetOrAdd(categoryName, this.CreateLoggerImplementation);
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
return;
}
this.loggers.Clear();
}
private static XmlDocument UpdateNodesWithOverridingValues(XmlDocument configXmlDocument, IEnumerable<NodeInfo> overridingNodes)
{
var additionalConfig = overridingNodes;
if (additionalConfig != null)
{
var configXDoc = configXmlDocument.ToXDocument();
foreach (var nodeInfo in additionalConfig)
{
var node = configXDoc.XPathSelectElement(nodeInfo.XPath);
if (node != null)
{
if (nodeInfo.NodeContent != null)
{
node.Value = nodeInfo.NodeContent;
}
AddOrUpdateAttributes(node, nodeInfo);
}
}
return configXDoc.ToXmlDocument();
}
return configXmlDocument;
}
/// <summary>
/// Rewrites the information of the node specified by xpath expression.
/// </summary>
/// <param name="configXml">The log4net config in xml.</param>
/// <param name="configurationSection">The configuration section.</param>
/// <returns>The xml configuration with overwritten nodes if any</returns>
private static XmlDocument UpdateNodesWithAdditionalConfiguration(XmlDocument configXml, IConfigurationSection configurationSection)
{
var additionalConfig = configurationSection.ToNodesInfo();
if (additionalConfig != null)
{
var configXDoc = configXml.ToXDocument();
foreach (var nodeInfo in additionalConfig)
{
var node = configXDoc.XPathSelectElement(nodeInfo.XPath);
if (node != null)
{
if (nodeInfo.NodeContent != null)
{
node.Value = nodeInfo.NodeContent;
}
AddOrUpdateAttributes(node, nodeInfo);
}
}
return configXDoc.ToXmlDocument();
}
return configXml;
}
/// <summary>
/// Adds or updates the attributes specified in the node information.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="nodeInfo">The node information.</param>
private static void AddOrUpdateAttributes(XElement node, NodeInfo nodeInfo)
{
if (nodeInfo?.Attributes != null)
{
foreach (var attribute in nodeInfo.Attributes)
{
var nodeAttribute = node.Attributes()
.FirstOrDefault(a => a.Name.LocalName.Equals(attribute.Key, StringComparison.OrdinalIgnoreCase));
if (nodeAttribute != null)
{
nodeAttribute.Value = attribute.Value;
}
else
{
node.SetAttributeValue(attribute.Key, attribute.Value);
}
}
}
}
/// <summary>
/// Parses log4net config file.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>The <see cref="XmlElement"/> with the log4net XML element.</returns>
private static XmlDocument ParseLog4NetConfigFile(string filename)
{
using (FileStream fp = File.OpenRead(filename))
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit
};
var log4netConfig = new XmlDocument();
using (var reader = XmlReader.Create(fp, settings))
{
log4netConfig.Load(reader);
}
return log4netConfig;
}
}
/// <summary>
/// Tries to retrieve the assembly from a "Startup" type found in the stack trace.
/// </summary>
/// <returns>Null for NetCoreApp 1.1, otherwise, Assembly of Startup type if found in stack trace.</returns>
private static Assembly GetCallingAssemblyFromStartup()
{
#if NETCOREAPP1_1
return null;
#else
var stackTrace = new System.Diagnostics.StackTrace(2);
for (int i = 0; i < stackTrace.FrameCount; i++)
{
var frame = stackTrace.GetFrame(i);
var type = frame.GetMethod()?.DeclaringType;
if (string.Equals(type?.Name, "Startup", StringComparison.OrdinalIgnoreCase))
{
return type.Assembly;
}
}
return null;
#endif
}
/// <summary>
/// Creates the logger implementation.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The <see cref="Log4NetLogger"/> instance.</returns>
private Log4NetLogger CreateLoggerImplementation(string name)
{
var options = new Log4NetProviderOptions
{
Name = name,
LoggerRepository = this.loggerRepository.Name,
};
options.ScopeFactory = new Log4NetScopeFactory(new Log4NetScopeRegistry());
return new Log4NetLogger(options);
}
}
}