initial commit
This commit is contained in:
+469
@@ -0,0 +1,469 @@
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
using log4net.Repository;
|
||||
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Entities;
|
||||
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Extensions;
|
||||
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Scope;
|
||||
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.Appender;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.Extensions.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The log4net provider class.
|
||||
/// </summary>
|
||||
/// <seealso cref="ILoggerProvider" />
|
||||
public class Log4NetProvider : ILoggerProvider, ISupportExternalScope
|
||||
{
|
||||
/// <summary>
|
||||
/// The loggers collection.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Log4NetLogger> loggers = new ConcurrentDictionary<string, Log4NetLogger>();
|
||||
|
||||
/// <summary>
|
||||
/// Prevents to dispose the object more than single time.
|
||||
/// </summary>
|
||||
private bool disposedValue = false;
|
||||
|
||||
/// <summary>
|
||||
/// The log4net repository.
|
||||
/// </summary>
|
||||
public ILoggerRepository Repository;
|
||||
|
||||
/// <summary>
|
||||
/// The provider options.
|
||||
/// </summary>
|
||||
private Log4NetProviderOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// The external logging scope provider.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reading the offical logging implementations, it seems like we need to handle the case that this might never be set.
|
||||
/// We handle it with a NullScopeProvider instead of null checks, to make the process of implementing interfaces like
|
||||
/// <see cref="ILog4NetLoggingEventFactory"/> less error prone for consumers.
|
||||
/// </remarks>
|
||||
public IExternalScopeProvider ExternalScopeProvider { get; private set; } = NullScopeProvider.Instance;
|
||||
|
||||
/// <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.SetOptionsIfValid(options);
|
||||
|
||||
Assembly loggingAssembly = GetLoggingReferenceAssembly();
|
||||
|
||||
this.CreateLoggerRepository(loggingAssembly)
|
||||
.ConfigureLog4NetLibrary(loggingAssembly);
|
||||
}
|
||||
|
||||
#region IOC implementation
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">A reference to the IOC service collection.</param>
|
||||
public Log4NetProvider(IServiceProvider serviceCollection)
|
||||
: this(new Log4NetProviderOptions(), serviceCollection)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="log4NetConfigFileName">The log4NetConfigFile.</param>
|
||||
/// <param name="serviceProvider">A reference to the IOC service collection.</param>
|
||||
public Log4NetProvider(string log4NetConfigFileName, IServiceProvider serviceProvider)
|
||||
: this(new Log4NetProviderOptions(log4NetConfigFileName), serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Log4NetProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="serviceProvider">A reference to the IOC service collection.</param>
|
||||
/// <exception cref="ArgumentNullException">options</exception>
|
||||
/// <exception cref="NotSupportedException">Watch cannot be true when you are overwriting config file values with values from configuration section.</exception>
|
||||
public Log4NetProvider(Log4NetProviderOptions options, IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider;
|
||||
|
||||
this.SetOptionsIfValid(options);
|
||||
|
||||
Assembly loggingAssembly = GetLoggingReferenceAssembly();
|
||||
|
||||
this.CreateLoggerRepository(loggingAssembly)
|
||||
.ConfigureLog4NetLibrary(loggingAssembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds a reference to the IOC Service Provider
|
||||
/// </summary>
|
||||
private IServiceProvider serviceProvider;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes the instance of the <see cref="Log4NetProvider"/> object.
|
||||
/// </summary>
|
||||
~Log4NetProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <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 (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.Repository.Shutdown();
|
||||
this.loggers.Clear();
|
||||
|
||||
serviceProvider = null;
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates configuration nodes overriding values if required.
|
||||
/// </summary>
|
||||
/// <param name="configXmlDocument">The configuration file XML document.</param>
|
||||
/// <param name="overridingNodes">The overriding values available</param>
|
||||
/// <returns>An <see cref="XmlDocument"/> within the overriding values replaced.</returns>
|
||||
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>
|
||||
/// 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 stream = File.OpenRead(filename))
|
||||
{
|
||||
var settings = new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Prohibit
|
||||
};
|
||||
|
||||
var log4netConfig = new XmlDocument();
|
||||
using (var reader = XmlReader.Create(stream, 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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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 loggerOptions = new Log4NetProviderOptions
|
||||
{
|
||||
Name = name,
|
||||
LoggerRepository = this.Repository.Name,
|
||||
OverrideCriticalLevelWith = this.options.OverrideCriticalLevelWith,
|
||||
LoggingEventFactory = this.options.LoggingEventFactory ?? new Log4NetLoggingEventFactory(),
|
||||
LogLevelTranslator = this.options.LogLevelTranslator ?? new Log4NetLogLevelTranslator(),
|
||||
};
|
||||
|
||||
return new Log4NetLogger(loggerOptions, ExternalScopeProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current executing assembly considering the target framework.
|
||||
/// </summary>
|
||||
/// <returns>The assembly to be used as the reference logging assembly.</returns>
|
||||
private static Assembly GetLoggingReferenceAssembly()
|
||||
{
|
||||
Assembly assembly = null;
|
||||
|
||||
assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
return assembly ?? GetCallingAssemblyFromStartup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that provided options combinations are valid, and sets the class field if everything is ok.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to validate.</param>
|
||||
/// <exception cref="NotSupportedException">
|
||||
/// Throws when the Watch option is set and there are properties to override.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Throws when the options parameter is null.
|
||||
/// </exception>
|
||||
private void SetOptionsIfValid(Log4NetProviderOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
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.");
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the log4net library using the available configuration data.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to be used on the configuration.</param>
|
||||
private Log4NetProvider ConfigureLog4NetLibrary(Assembly assembly)
|
||||
{
|
||||
if (this.options.UseWebOrAppConfig)
|
||||
{
|
||||
XmlConfigurator.Configure(this.Repository);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!this.options.ExternalConfigurationSetup)
|
||||
{
|
||||
string fileNamePath = CreateLog4NetFilePath(assembly);
|
||||
if (this.options.Watch)
|
||||
{
|
||||
XmlConfigurator.ConfigureAndWatch(
|
||||
this.Repository,
|
||||
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.Repository, configXml.DocumentElement);
|
||||
}
|
||||
}
|
||||
|
||||
this.InjectServices();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wires up specific adapters for IOC support
|
||||
/// </summary>
|
||||
private void InjectServices()
|
||||
{
|
||||
if (this.Repository is null)
|
||||
return;
|
||||
|
||||
IEnumerable<IAppenderServiceProvider> adapters =
|
||||
this.Repository
|
||||
.GetAppenders()
|
||||
.OfType<IAppenderServiceProvider>();
|
||||
|
||||
foreach (IAppenderServiceProvider adapter in adapters)
|
||||
adapter.ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the log4net.config file path.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to be used when the configuration indicate to use the current assembly.</param>
|
||||
/// <returns>The full path to the log4net.config file.</returns>
|
||||
private string CreateLog4NetFilePath(Assembly assembly)
|
||||
{
|
||||
string fileNamePath = this.options.Log4NetConfigFileName;
|
||||
if (!Path.IsPathRooted(fileNamePath))
|
||||
{
|
||||
fileNamePath = Path.Combine(AppContext.BaseDirectory, fileNamePath);
|
||||
}
|
||||
|
||||
return Path.GetFullPath(fileNamePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates the logger repository using the given assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly to be used to create de repository.</param>
|
||||
private Log4NetProvider CreateLoggerRepository(Assembly assembly)
|
||||
{
|
||||
Type repositoryType = typeof(log4net.Repository.Hierarchy.Hierarchy);
|
||||
|
||||
if (!string.IsNullOrEmpty(this.options.LoggerRepository))
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Repository = LogManager.GetRepository(this.options.LoggerRepository);
|
||||
if (this.options.ExternalConfigurationSetup)
|
||||
{
|
||||
// The logger repository is already configured. We can exit here.
|
||||
return this;
|
||||
}
|
||||
}
|
||||
catch (log4net.Core.LogException)
|
||||
{
|
||||
// The logger repository is not defined outside the extension.
|
||||
this.Repository = null;
|
||||
}
|
||||
|
||||
if (this.Repository == null)
|
||||
{
|
||||
this.Repository =
|
||||
LogManager.CreateRepository(this.options.LoggerRepository, repositoryType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Repository =
|
||||
LogManager.CreateRepository(assembly, repositoryType);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
|
||||
{
|
||||
ExternalScopeProvider = scopeProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user