Extensible Logging Library For Sharepoint With ULS Logging Support

Posted by Ahmed Tarek Hasan on 2/22/2013 10:05:00 PM with No comments
What I am presenting here is a library used for logging in Sharepoint 2010.

What makes this library a good choice?
  1. Its code is clean and maintainable
  2. Its code is extensible as you can easily add support to many third party logging services with just some few code lines
  3. It provides a single entry point static class for usage instead of many classes

Which logging services does this library currently support?
Currently the library supports the OOTB Unified Logging Service (ULS) but for sure you can add the support for other logging services like log4net and others with just some few lines of code.


Is the code simple or complicated?
  1. The first code part: The main library code is simple and follow the "Bridge" and "Factory" design patterns. I had already wrote a post on these two design patterns with an example of how to use them. If you didn't read this post and it is your first time to hear about these two design patterns, I really encourage you to read One Of The Methodologies To Write Clean, Maintainable & Extensible Software
  2. The second code part: The main entry point static class which enables you to use the library in an easy way. This class is so simple and clear
  3. The third code part: The implementations of the logging services you wish the library can support. The complexity of this part depends on the logging service itself. Some may be simple and others may be complex

Can we see the code?

LoggerDefinitions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DevelopmentSimplyPut.CommonUtilities.Logging
{
    public enum LoggingLevel
    {
        Debug = 0,
        Info = 1,
        Warn = 2,
        Error = 3,
        Fatal = 4
    }

    public interface ILogger
    {
        void Configure(Dictionary<string,object> settings);
        void Log(string message, LoggingLevel level);
        void Log(Exception exception, LoggingLevel level);
        void Log(Exception exception, string message, LoggingLevel level);
    }

    public abstract class Logger : ILogger
    {
        public abstract void Configure(Dictionary<string, object> settings);
        public abstract void Log(string message, LoggingLevel level);
        public abstract void Log(Exception exception, LoggingLevel level);
        public abstract void Log(Exception exception, string message, LoggingLevel level);
    }
}

LoggerFactory.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DevelopmentSimplyPut.CommonUtilities.Logging.ULS;

namespace DevelopmentSimplyPut.CommonUtilities.Logging
{
    public static class LoggerFactory
    {
        public static ILogger GetLoggerInstance(SystemLoggerType loggerType, Dictionary<string, object> settings)
        {
            ILogger result = null;

            switch (loggerType)
            {
                case SystemLoggerType.ULS:
                    result = ULSLogger.Current;
                    break;
                default:
                    result = ULSLogger.Current;
                    break;
            }

            result.Configure(settings);
            return result;
        }
    }
}

SystemLogger.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DevelopmentSimplyPut.CommonUtilities.Helpers;

namespace DevelopmentSimplyPut.CommonUtilities.Logging
{
    public enum SystemLoggerType
    {
        ULS = 0
        //, Log4Net = 1
    }

    public static class SystemLogger
    {
        private static ILogger logger;
        private static void SetLogger(SystemLoggerType loggerType)
        {
            switch (loggerType)
            {
                case SystemLoggerType.ULS:
                    Dictionary<string, object> settings = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
                    settings.Add("ProductDiagName", "DevelopmentSimplyPut");
                    logger = LoggerFactory.GetLoggerInstance(SystemLoggerType.ULS, settings);
                    break;
            }
        }

        /// <summary>
        /// Gets current initialized system logger. If not already initialized, initializes a new default logger and returns it.
        /// </summary>
        public static ILogger Logger
        {
            get
            {
                if (logger == null)
                {
                    SetLogger(SystemLoggerType.ULS);
                }
                return logger;
            }
        }
        /// <summary>
        /// Resets current system logger to the default logger and finally returns this logger instance.
        /// </summary>
        /// <returns></returns>
        public static ILogger ResetLogger()
        {
            return ResetLogger(SystemLoggerType.ULS);
        }
        /// <summary>
        /// Resets current system logger to a specific logger type and finally returns this logger instance.
        /// </summary>
        /// <param name="loggerType"></param>
        /// <returns></returns>
        public static ILogger ResetLogger(SystemLoggerType loggerType)
        {
            SetLogger(loggerType);
            return logger;
        }
    }
}

ULSLogger.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using System.Globalization;

namespace DevelopmentSimplyPut.CommonUtilities.Logging.ULS
{
    public class ULSLogger : Logger
    {
        private string productDiagName = string.Empty;
        private ULSLogger()
        {
        }
        
        private static ULSLoggingService CurrentServices
        {
            get;
            set;
        }

        private static ULSLogger current;
        public static ULSLogger Current
        {
            get
            {
                if (current == null)
                {
                    current = new ULSLogger();
                }
                return current;
            }
        }
        
        public override void Configure(Dictionary<string, object> settings)
        {
            if (settings != null && settings.Count > 0 && settings.Any(record => record.Key.ToUpperInvariant() == "ProductDiagName".ToUpperInvariant()))
            {
                productDiagName = Convert.ToString(settings["ProductDiagName"]);
                CurrentServices = new ULSLoggingService(productDiagName);
            }
        }

        public override void Log(string message, LoggingLevel level)
        {
            Log(null, message, level);
        }
        public override void Log(Exception exception, LoggingLevel level)
        {
            Log(exception, exception.Message + Environment.NewLine + Environment.NewLine + exception.StackTrace, level);
        }
        public override void Log(Exception exception, string message, LoggingLevel level)
        {
            string msg =
                (null == exception) ? (message) :
                string.Format(CultureInfo.InvariantCulture, "{0}\n\n{1}\n\n{2}", message, exception.Message, exception.StackTrace);

            CurrentServices.Log(msg, level);
        }
    }
}

ULSLoggingService.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;

namespace DevelopmentSimplyPut.CommonUtilities.Logging.ULS
{
    public class ULSLoggingService : SPDiagnosticsServiceBase
    {
        public ULSLoggingService(string productName) : base(productName, SPFarm.Local)
        {
        }
        
        protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()
        {
            List<SPDiagnosticsArea> areas = new List<SPDiagnosticsArea>
            {
                new SPDiagnosticsArea
                (Name, new List<SPDiagnosticsCategory>
                    {
                        new SPDiagnosticsCategory("Debug", TraceSeverity.None, EventSeverity.None),
                        new SPDiagnosticsCategory("Error", TraceSeverity.Unexpected, EventSeverity.Error),
                        new SPDiagnosticsCategory("Info", TraceSeverity.Monitorable, EventSeverity.Information),
                        new SPDiagnosticsCategory("Warn", TraceSeverity.Medium, EventSeverity.Warning),
                        new SPDiagnosticsCategory("Fatal", TraceSeverity.High, EventSeverity.ErrorCritical),
                    }
                )
            };

            return areas;
        }

        private void LogDebug(string message)
        {
            SPDiagnosticsCategory category = Areas[Name].Categories["Debug"];
            WriteTrace(0, category, category.TraceSeverity, message);
        }
        private void LogError(string message)
        {
            SPDiagnosticsCategory category = Areas[Name].Categories["Error"];
            WriteTrace(0, category, category.TraceSeverity, message);
        }
        private void LogInfo(string message)
        {
            SPDiagnosticsCategory category = Areas[Name].Categories["Info"];
            WriteTrace(0, category, category.TraceSeverity, message);
        }
        private void LogWarn(string message)
        {
            SPDiagnosticsCategory category = Areas[Name].Categories["Warn"];
            WriteTrace(0, category, category.TraceSeverity, message);
        }
        private void LogFatal(string message)
        {
            SPDiagnosticsCategory category = Areas[Name].Categories["Fatal"];
            WriteTrace(0, category, category.TraceSeverity, message);
        }
        public void Log(string message, LoggingLevel level)
        {
            switch (level)
            {
                case LoggingLevel.Debug:
                    LogDebug(message);
                    break;
                case LoggingLevel.Error:
                    LogError(message);
                    break;
                case LoggingLevel.Fatal:
                    LogFatal(message);
                    break;
                case LoggingLevel.Info:
                    LogInfo(message);
                    break;
                case LoggingLevel.Warn:
                    LogWarn(message);
                    break;
                default:
                    LogDebug(message);
                    break;
            }
        }
    }
}


How to use this library?
catch (NullReferenceException ex)
{
 SystemLogger.Logger.Log(ex, LoggingLevel.Error);
}

or

catch (NullReferenceException ex)
{
 SystemLogger.Logger.Log(ex, "This is custom error message for logging", LoggingLevel.Error);
}

or

catch (NullReferenceException ex)
{
 SystemLogger.Logger.Log("This is custom error message for logging", LoggingLevel.Warn);
}

or

SystemLogger.Logger.Log("This is just a hint message for logging", LoggingLevel.Info);

This is just a sample of what you can do using this library. You can browse through the code and you will get the whole thing. For sure you can customize the code to add any other functionality you wish to have or modify an existing one.

This code is already used in commercial solutions and it proved to be working efficiently among the regular needs but you can apply your changes as I said before.


That's it, hope you find this library useful and I will be waiting for your feedback.
Bye.



Categories: , , ,