Why LogWriter fails when a TraceListener fails ?

Topics: Logging Application Block
Jul 25, 2012 at 10:08 PM
Edited Jul 26, 2012 at 7:00 PM

I am using version 5.0.414.0.

I have configured my service to use a RollingFileTraceListener. The listener is configured to write to a file which is on a network share.

I purposefully (for testing) provided a share location where the process does not have access to write to. Hence I expect the RollingFileTraceListener to fail when it tries to write the log.

HOWEVER, I do not expect the log statements (in my application) to fail. The application should continue even if the log entry is not written to any destination.

Unfortunately, the statement below is failing (Microsoft.Practices.ServiceLocation.ActivationException) -

var logger = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();

The above statement fails because the constructor of the RollingFlatFileTraceListener throws the access-denied error.

I hope that this is not by-design (that would be bad design IMHO). I am hoping that there is some configuration mistake that I am making.

Is there any configuration which will let me specify that the application must NOT fail even if some TraceListener is not configured correctly (or fails randomly)? 

Why I am saying it’s not a good design is – 

I am expecting that the logging framework is designed as a pub-sub system.

Any error in the subscribers (trace listeners) should not be able to bring down the publishers (application publishing log statements).

Given that the listeners could be writing to DBs, network shares etc. the chance of failure is relatively high.

Jul 26, 2012 at 6:26 AM

Thanks for reporting this issue.

First let me address the way the Block is designed and how it should behave.  

When the Write method is called on the LogWriter the LogEntry is evaluated and, depending on the various settings, filters, etc., the message is written to all trace listeners that are configured for the given category.  If an exception occurs it is caught and if the errors special sources is configured the exception that occurred along with the original LogEntry information is attempted to be written to the errors special source.  I always recommend setting the errors special source to a simple trace listener (e.g. Flat File Trace Listener) that writes to a local drive location where permissions should exist.  If writing to the errors special source fails then the exception is caught and, if configured, a performance counter is fired.

This is how it works with the FormattedEventLogTraceListener and the FormattedDatabaseTraceListener (and others).  The issue that you've encountered is during the setup of the Logging Block in the Enterprise Library container.  It appears for the file trace listeners some initial setup is occurring during object construction (i.e. create directory) and this setup is throwing an exception causing the instantiation to fail and the LogWriter to not be resolved from the container.

So to sum up the affected listeners are file based trace listeners and the issue occurs when creating a directory if the directory specified does not exist.  So, the issue does not affect all trace listeners in all permission scenarios only the previously mentioned scenario.

To me, this looks like a defect.  I didn't see an issue created for this in the issue log so I created one.

How to work around this issue if it is affecting you?  I can think of 3 options:

  1. Work around the exception during resolution of the LogWriter
  2. Modify the Enterprise Library Source Code
  3. Create new Trace Listeners such that the problem does not occur

In option 1 you could create a LogWriter dispenser (perhaps singleton) and internally catch the exception and then attempt to configure the logging block programmatically.  If that fails you could create your own LogWriter directly.  A partial, crude sample version looks like this:

    class Program
    {
        static void Main(string[] args)
        {
            LogWriter logWriter = null;
            try
            {
                logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
            }
            catch (Exception e)
            {
                try
                {
                    ConfigureLoggingOverride();
                    logWriter = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
                }
                catch (Exception)
                {
                    logWriter = new MyLogWriter();
                }
            }

            logWriter.Write("Test", "General");
        }

        static void ConfigureLoggingOverride()
        {
            var builder = new ConfigurationSourceBuilder();

            builder.ConfigureLogging()
                   .WithOptions
                     .DoNotRevertImpersonation()
                   .LogToCategoryNamed("General")
                   .WithOptions.SetAsDefaultCategory()
                     .SendTo.FlatFile("Flat File Trace Listener")
                       .FormatWith(new FormatterBuilder()
                         .TextFormatterNamed("Text Formatter")
                           .UsingTemplate("Timestamp: {timestamp} {message}{newline})}"))
                         .ToFile(@"trace.log") // someplace safe to log to
                     ;

            var configSource = ConfigurationSourceFactory.Create();
            builder.UpdateConfigurationWithReplace(configSource);
            EnterpriseLibraryContainer.Current
              = EnterpriseLibraryContainer.CreateDefaultContainer(configSource);
        }
    }
    public class MyLogWriter : LogWriter
    {
        public override Microsoft.Practices.EnterpriseLibrary.Logging.Filters.ILogFilter GetFilter(string name)
        {
            return null;
        }

        public override T GetFilter<T>(string name)
        {
            return null;
        }

        public override T GetFilter<T>()
        {
            return null;
        }

        public override IEnumerable<LogSource> GetMatchingTraceSources(LogEntry logEntry)
        {
            throw new NotImplementedException();
        }

        public override bool IsLoggingEnabled()
        {
            return true;
        }

        public override bool IsTracingEnabled()
        {
            return false;
        }

        public override bool ShouldLog(LogEntry log)
        {
            return true;
        }

        public override IDictionary<string, LogSource> TraceSources
        {
            get { return null; }
        }

        public override void Write(LogEntry log)
        {
            try
            {
                // Custom Logging could be a file or event log
            }
            catch (Exception e)
            {
            }
        }
    }

Needless to say, catching an exception is not very elegant.  The second option is perfectly viable however many people do not wish to take it upon themselves to build and maintain Enterprise Library source code.

The third option might be more palatable since you can just plug in different trace listeners without having to rebuild the entire Enterprise Library core.  To that end, I've implemented a quick fix by creating custom versions of RollingFlatFileTraceListener and FlatFileTraceListener.  In order to minimize the changes, I've caught the exception in the constructor and then throw that exception when/if the Write (TraceData internally) method is called.  This kicks off the "normal" Enterprise Library exception handling where the original LogEntry as well as the exception information will be logged to the errors special source (so the issue can be spotted and resolved).  The Samples.TraceListeners project contains those new trace listeners.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com