Tuesday, April 30, 2013

Creating a logging service using the Microsoft Enterprise Library Logging Application Block

As part of my recent foray into creating various Windows services, I've come across the need for logging (like all serious apps do) and decided to use the Microsoft Enterprise Library Logging Application Block. We used it at a previous company and it got very good reviews from the developers there who used it. Unfortunately, all of the tutorials out there are a bit useless when it comes to doing anything real with the Logging Application Block, like using custom configuration. Fortunately, it was easy enough to figure out how to get customized configuration of logging working just by looking in the assembly and writing some unit tests to experiment.

For my needs, I like to keep all of my configuration as compartmentalized as possible. To that end, I usually end up using .NET Configuration files on a per-assembly basis. Fortunately, this works very well with the Logging Application Block because you can configure logging with arbitrary Configuration files. To get custom logging working easily in Visual Studio, do the following:
  1. Open Visual Studio (I'm using 2010)
  2. Install NuGet from the Extension Manager, if you haven't already.
  3. Install EnterpriseLibrary.Config from the Extension Manager if you haven't already.
  4. Create your solution and project that requires logging.
  5. If your project (web or application) comes with a .config file already (i.e. Web.config or App.config respectively) you can use that, otherwise, you can create an app.config file for the assembly in which you're creating your logging service.
  6. Once you've figured out which app.config (or Web.config) file you're going to be using (i.e. the same assembly where you're placing your logging class, right click on the .config file. You should see an 'Edit configuration file' entry with an orange logo (provided by the add-in installed in Step 3 above).
  7. Follow any other tutorial out there on the internet for configuring the Logging Application Block. (repeating such information here would be redundant and pointless). There's one such article here.
  8. Now that you've got your configuration, create a class to be your logging service wrapper.
  9. Figure out where your application config file is going to end up after your application is built.
  10. In your logging service wrapper, create a Microsoft.Practices.EnterpriseLibrary.Common.Configuration.FileConfigurationSource, giving the configuration file path as the sole parameter to the constructor.
  11. Create a new Microsoft.Practices.EnterpriseLibrary.Logging.LogWriterFactory, providing the FileConfigurationSource you just created as the sole parameter. You'll probably want to store this as a static object, because we only need a single factory.
  12. For each instance of your logging service, use this static factory that you've created to create a LogWriter.
  13. Implement whatever methods you need to wrap the logging service.
  14. You're done.
Your code should now look something like the following :
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Text;
    using Interfaces;
    using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Support.Extensions;

    /// 
    /// A logging services that uses the Microsoft Enterprise Patterns and 
    /// Practices library to provide logging support.
    /// 
    public class MicrosoftPracticesLoggingService : ILoggingService
    {
        /// 
        /// The log writer factory
        /// 
        private static readonly LogWriterFactory LogWriterFactory;

        /// 
        /// Initializes the  class.
        /// 
        static MicrosoftPracticesLoggingService()
        {
            string configFilePath = typeof(MicrosoftPracticesLoggingService).Assembly.GetCodeBaseFile().FullName + ".config";

            if (File.Exists(configFilePath) == false)
            {
                throw new InvalidOperationException(String.Format("Configuration file could not be found at '{0}'", configFilePath));
            }

            FileConfigurationSource configSource = new FileConfigurationSource(configFilePath);

            LogWriterFactory = new LogWriterFactory(configSource);
        }

        /// 
        /// The log writer used by this service to write to logs.
        /// 
        private readonly LogWriter logWriter;

        /// 
        /// Initializes a new instance of the  class.
        /// 
        public MicrosoftPracticesLoggingService()
        {
            this.logWriter = LogWriterFactory.Create();
        }

        #region Implementation of ILoggingService

        /// 
        [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1604:ElementDocumentationMustHaveSummary", Justification = "InheritDoc")]
        [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1611:ElementParametersMustBeDocumented", Justification = "InheritDoc")]
        public void LogException(Exception ex, string messageFormat, params object[] parameters)
        {
            if (ex == null)
            {
                throw new ArgumentNullException("ex");
            }

            if (messageFormat == null)
            {
                throw new ArgumentNullException("messageFormat");
            }

            StringBuilder messageBuilder = new StringBuilder();

            messageBuilder.AppendFormat(messageFormat, parameters).AppendLine();
            messageBuilder.AppendLine(ex.StackTrace);

            if (ex.InnerException != null)
            {
                Exception root = ex;

                while (root.InnerException != null)
                {
                    root = root.InnerException;
                }

                messageBuilder.AppendLine("Caused by:");
                messageBuilder.AppendLine(root.StackTrace); 
            }

            LogEntry logEntry = new LogEntry
                {
                    Message = messageBuilder.ToString(),
                    Severity = TraceEventType.Error
                };

            this.logWriter.Write(logEntry);
        }

        #endregion
    }

No comments: