Entlib Configuration for Library Applications

Topics: Enterprise Library Core, Logging Application Block
Jan 18, 2011 at 7:45 AM

I have an architectural question.

We use Entlib Logging 5.0 as our primary component to log messages in every .NET Application. Since we also have many "library components" that help applications with communication to the mainframe and so on, these library components also have to log messages and should use Entlib for this.

But now we have the problem, that only the App (Windows oder Web) can specify a configuration file with the correct Categories and Trace Listeners. So every app has to know to which Category the "library component" logs its messages to and has to configure it properly. This is error prone and can easly lead to lost messages or wrong configurations for these library components.

What would be the recommended way to centralize the logging settings for each library component so that any application does only have to care about its loggings settings?

We tried to use the fluent configuration interface in the "library component", but that leads to the problem, that it doesn't merge the current logging settings of the hosting app with the additional settings from the component, they are replaced an the original settings are away.

What is the best way to do this and to keep the coupling between the app and the library component as lose as possible?

Thanks for helping out

Marius Filipowski
ITERGO

 

Jan 18, 2011 at 9:41 AM

The way I understand it what you want is to have a centralized configuration for your Logging for all your App right? If yes, see if utilizing Enterprise Library 5.0 - SQL Configuration Source will help. Or you can try using a single configuration file for all your App's logging configurations, see if this blog may help too http://blogs.msdn.com/b/tomholl/archive/2006/04/02/entlib2externalconfig.aspx.

Gino Terrado
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Jan 18, 2011 at 10:23 AM

Thank you for the help.

Not exactly. All apps should have their own logging config. That is ok. But these apps use different library components (libraries like ADO.NET or System.Data, but written by us) that have also their own entlib logging configuration. And that would mean that all (main) applications have to specify the configurations elements for their own logging config and ALSO include the logging config for the library. The latter is what we would like to avoid, so that every library application can specify its logging settings in a central location (in code or another config file or whatever is best for this).

I hope this clarifies our scenario.

Thx

Jan 18, 2011 at 11:37 AM

Ah so its really more like merging two different Logging configuration setting, right? If it is, then I believe you have the same scenario with this related thread - http://entlib.codeplex.com/Thread/View.aspx?ThreadId=213237. Also check if the stated resolution from that thread will work for you. HTH.

Gino Terrado
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Jan 18, 2011 at 1:19 PM

Thank you for pointing out this example.

But as I've understood, the application still has to know that there's another config that has to be loaded and it has to specify it as a parent source. This is a very tight coupling and also error prone becaue the logging wouldn't work if the app "forgets" to configure it correctly.

And if there were 2 library components with their own configs, it would not work ,because as I've understood, there can be only one parent config.

Isn't there a way to merge configs in code? Or is it possible to specifiy for a C#-Component that it loads its Entlibconfiguration from another store without changing the main config?

Jan 19, 2011 at 1:09 AM

Hi, if you use the fluent configuration API, you can add an extra code so that it doesn't overwrite your entire logging config:

var builder = new ConfigurationSourceBuilder();

builder.ConfigureLogging();
  //configure logging

var configSource = ConfigurationSourceFactory.Create();
builder.UpdateConfigurationWithReplace(configSource);
EnterpriseLibraryContainer.Current 
  = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);

The code I highlighted creates an IConfigurationSource object from the default configuration that is in use.  This way, it will merge the logging configuration you created using the Fluent API with the configuration file.

 

Sarah Urmeneta
Global Technologies and Solutions
Avanade, Inc.
entlib.support@avanade.com

Jan 19, 2011 at 5:54 AM

Actually, no it won't. Using the fluent API overwrites the entire block you've configured. So if they have App level config and then use the fluent API to configure the library logging, at the end there will be only the library logging, the app level stuff will be completely overwritten.

That's why the API name includes "WithReplace".

 

Jan 19, 2011 at 6:56 AM
Edited Jan 19, 2011 at 7:15 AM
ctavares wrote:

Actually, no it won't. Using the fluent API overwrites the entire block you've configured. So if they have App level config and then use the fluent API to configure the library logging, at the end there will be only the library logging, the app level stuff will be completely overwritten.

That's why the API name includes "WithReplace".

Yes, that's what we also observed. This was our first idea but it is isn't intended to be used in this scenario. Just an idea: Could it be possible to use the (parent) merging mechanism directly from code to update an existing config in memory?

Jan 20, 2011 at 10:50 AM

I researched a little bit and I've found a solution that I'd like to be reviewed as I am not an Unity and Entlib expert.

My idea is:

  • The main app uses Entlib as usual with its own config
  • Any library apps has it's own entlib config that is loaded separately

To achieve this, every library app creates it own UnityContainer as a child container of the Main app's container. This container is initialized the same way as the entlib does it but with a different IConfigurationSource. The library app uses this child container to resolve its LogWriter instance that is in the Childcontainer. Now it can log messages without the main app ever knowing that there's another config.

Is this idea ok? Will there be any performance problems? Did I forget anything?

Here's my sample code that demonstrates using two LogWriter in parallel in a consoleapp

class Program
    {
        // Caching the instances of the child container   
        private static readonly IUnityContainer myContainer = CreateChildContainer();
        // Shortcut to the child logwriter
        private static LogWriter logWriter;

        // Creation of the Child Container
        private static IUnityContainer CreateChildContainer()
        {
            var cnt = (IUnityContainer)EnterpriseLibraryContainer.Current.GetInstance(typeof(IUnityContainer));
            // Here it is
            IUnityContainer childContainer = cnt.CreateChildContainer();
            // Reading another config
            var configSourceP = new FileConfigurationSource("App2.config");

             // That's just copied from the Entlib initcode
            var configurator = new UnityContainerConfigurator(childContainer);
            var reconfiguringEventSource = configurator as IContainerReconfiguringEventSource ??
                                           new NullContainerReconfiguringEventSource();

            ConfigureContainer(
                TypeRegistrationsProvider.CreateDefaultProvider(configSourceP, reconfiguringEventSource),
                configurator,
                configSourceP);
            IServiceLocator childLocator = new UnityServiceLocator(childContainer);

            logWriter = (LogWriter)childLocator.GetInstance(typeof(LogWriter));
            return myContainer;
        }

        static void Main(string[] args)
        {
            // Writing a message in the child writer, simulates an library app
            logWriter.Write("ChildMessage","Kat1");
            logWriter.Write("ChildMessage", "Unknown");
            // Writing a message with the "normal" LogWriter hidden
            // behind the Logger facade => simulates Main Application
            Logger.Write("Test");
            // just again the library
            logWriter.Write("ChildMessage2", "Kat1");
            // and again the Main App
            Logger.Write("Test");
            
        }

        // Helper method taken from Entlib source
        public static void ConfigureContainer(ITypeRegistrationsProvider locator,
            IContainerConfigurator configurator,
            IConfigurationSource configSource)
        {
            if (configurator == null) throw new ArgumentNullException("configurator");

            configurator.RegisterAll(configSource, locator);
        }

Here's the Main.config

<configuration>
  <configSections>
    <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" requirePermission="true"/>
  </configSections>
  <loggingConfiguration name="ITERGO Logging" defaultCategory="Kat1" logWarningsWhenNoCategoriesMatch="true">
    <listeners>
      <add name="Kat1Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" fileName="kat1.log" header="" footer="" formatter="Text Formatter"/>
      <add name="Error Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" fileName="errors.log"/>
    </listeners>
    <formatters>
      <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" template="Message: " name="Text Formatter"/>
    </formatters>
    <categorySources>
      <add switchValue="All" name="Kat1">
        <listeners>
          <add name="Kat1Listener"/>
        </listeners>
      </add>
    </categorySources>
    <specialSources>
      <allEvents switchValue="All" name="All Events"/>
      <notProcessed switchValue="All" name="Unprocessed Category"/>
      <errors switchValue="All" name="Logging Errors &amp; Warnings">
        <listeners>
          <add name="Error Flat File Trace Listener"/>
        </listeners>
      </errors>
    </specialSources>
  </loggingConfiguration>
</configuration>

And here is the library config App2.config

<configuration>
  <configSections>
    <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" requirePermission="true"/>
  </configSections>
  <loggingConfiguration name="ITERGO Logging" defaultCategory="Kat1" logWarningsWhenNoCategoriesMatch="true">
    <listeners>
      <add name="ChildListener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" fileName="child.log" header="" footer="" formatter="Text Formatter"/>
      <add name="Child Error Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" fileName="childerrors.log"/>
    </listeners>
    <formatters>
      <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.0.0, Culture=neutral, PublicKeyToken=50b054f0ddce64ee" template="Timestamp: {timestamp}{newline}&#xA;Message: {message}" name="Text Formatter"/>
    </formatters>
    <categorySources>
      <add switchValue="All" name="Kat1">
        <listeners>
          <add name="ChildListener"/>
        </listeners>
      </add>
    </categorySources>
    <specialSources>
      <allEvents switchValue="All" name="All Events"/>
      <notProcessed switchValue="All" name="Unprocessed Category"/>
      <errors switchValue="All" name="Logging Errors &amp; Warnings">
        <listeners>
          <add name="Child Error Flat File Trace Listener"/>
        </listeners>
      </errors>
    </specialSources>
  </loggingConfiguration>
</configuration>

Feb 8, 2011 at 12:02 AM

Marius,

Yes, the idea with using child containers is feasible. For performance implications, make sure to do your own perf testing in your environment.

Grigori