How to configure log4net when using shared assemblies?
I have several components that depend on a common component. Each of the components lives in its own assembly. All components use log4net for logging. All components are loaded into a single process space, but the order in which components are used changes. All external components load their respective log4net configurations upon first use, in order to try to send log data to their own. The generic component does not load any configuration. In addition, there is an obsolete component that does not use a common component. It also surpasses and loads its configuration on first use. I cannot directly touch the code or configuration of this legacy component.
The problem I am facing is that due to the components loading their configuration on first use, the last user loads the configuration. This leads to logging, but all log output ends up loading the last loaded configuration. Obviously, it's just ugly to look into your log file and write to it the records of other components.
I hit a partial solution using RepositoryAttribute and AliasRepositoryAttribute. Allows outfacing components to upload their configuration to their "repository repository" and effectively isolate themselves from each other. The legacy component now happily writes its logs without noise from my components, and my components happily write their logs without creating noise in other magazines.
I said a partial solution because there is still a logging situation for the common component. When using AliasRepositoryAttribute, the first component that loads and aliases, the common component receives all the log output, even if it was another component that called the common component. This is terrible since I will be missing important registration information in later components and I will have irrelevant registration information in the first journal.
The following code demonstrates the problem:
General: (stand for the common component)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using log4net; using log4net.Config; [assembly: Repository("CommonLib")] namespace CommonLib { public class CommonClass { static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public void DoCommon(string from) { Log.Debug("DoCommon:" + from); } } }
Library A: (stand up for one of the appearance components)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using CommonLib; using log4net; using log4net.Config; [assembly: Repository("ALib")] [assembly: AliasRepository("CommonLib")] namespace ALib { public static class LogPrep { static bool _loaded = false; public static void Ensure() { if (_loaded) return; var doc = new XmlDocument(); doc.LoadXml( @"<log4net xsi:noNamespaceSchemaLocation='http://csharptest.net/downloads/schema/log4net.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' debug='true'> <appender name='ConsoleAppender' type='log4net.Appender.ColoredConsoleAppender'> <mapping> <level value='DEBUG' /> <foreColor value='White' /> <backColor value='Green' /> </mapping> <layout type='log4net.Layout.PatternLayout'> <conversionPattern value='ALib: %d{yyyy MMM dd HH:mm:ss} [%p] %c{1} %mdc - %m%n' /> </layout> </appender> <root> <level value='DEBUG' /> <appender-ref ref='ConsoleAppender' /> </root> </log4net>"); XmlConfigurator.Configure(doc.DocumentElement); _loaded = true; } } public class AClass { static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public void DoA() { LogPrep.Ensure(); Log.Debug("DoA"); var common = new CommonClass(); common.DoCommon("A"); } } }
Library B: (stand for another frontend component)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using CommonLib; using log4net; using log4net.Config; [assembly: Repository("BLib")] [assembly: AliasRepository("CommonLib")] namespace BLib { public class BClass { static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public static class LogPrep { static bool _loaded = false; public static void Ensure() { if (_loaded) return; var doc = new XmlDocument(); doc.LoadXml( @"<log4net xsi:noNamespaceSchemaLocation='http://csharptest.net/downloads/schema/log4net.xsd' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' debug='true'> <appender name='ConsoleAppender' type='log4net.Appender.ColoredConsoleAppender'> <mapping> <level value='DEBUG' /> <foreColor value='White' /> <backColor value='Red' /> </mapping> <layout type='log4net.Layout.PatternLayout'> <conversionPattern value='BLib: %d{yyyy MMM dd HH:mm:ss} [%p] %c{1} %mdc - %m%n' /> </layout> </appender> <root> <level value='DEBUG' /> <appender-ref ref='ConsoleAppender' /> </root> </log4net>"); XmlConfigurator.Configure(doc.DocumentElement); _loaded = true; } } public void DoB() { LogPrep.Ensure(); Log.Debug("DoB"); var common = new CommonClass(); common.DoCommon("B"); } } }
Executable container: (stand for legacy component)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; using ALib; using BLib; using log4net; using log4net.Config; namespace TestLog4NetRepositories { class Program { private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); static void Main(string[] args) {
If you run this code, note that the general output is printed in green and never printed in red.
log4net: Creating repository [BLib] using type [log4net.Repository.Hierarchy.Hierarchy] log4net:ERROR Failed to alias repository [CommonLib] System.InvalidOperationException: Repository [CommonLib] is already aliased to repository [ALib]. Aliases cannot be redefined. at log4net.Core.DefaultRepositorySelector.AliasRepository(String repositoryAlias, ILoggerRepository repositoryTarget) at log4net.Core.DefaultRepositorySelector.LoadAliases(Assembly assembly, ILoggerRepository repository)
Although the executable container above shows A to B, in a real situation B may be up to A.
As I mentioned earlier, I have a more complex hierarchy, but the minimum problem I am facing is shown above.