How to setup Semantic Logging Application Block (SLAB)

Event Logging

Logging errors and information is a useful, if not absolutely necessary tool for developers to build and manage applications. Microsoft’s Enterprise Library contains the Semantic Logging Application Block (SLAB), which provides a consistently formatted output of log files to your SQL database. This article will walk you through the setup process for SLAB, which will log information to your SQL Server database

Installing Semantic Logging Application Block:
Install the Nuget package for ‘Semantic Logging Application Block – SQL Server Sink’ . (it will include the core Semantic Logging)
Adding an EventSource class to your application:
Now you need to override the EventSource class, which I have done in the ‘LogWriter’ class below. I’ve also created a ‘Logger’ class to make it easier to deal with LogWriter. It takes care of some repetitive work for me. I can just pass in a raw Exception to Logger, and it will utilize LogWriter to write it to the Traces table in my database. Nice and easy.

You can add this to your Lib folder in your UI (main process project):

using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;

namespace MySolution.WebApi.Lib
{
    public class Logger
    {
        public static readonly Logger Log = new Logger();
        public void Error(Exception ex)
        {
            string message = ex.Message;
            string innerMessage = "";
            if (ex.InnerException != null)
                innerMessage = ex.InnerException.Message;

            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            LogWriter.Log.Error(message, innerMessage, methName, stack);

        }
        public void Error(Exception ex, string addedMessage)
        {
            string message = addedMessage + " :: " + ex.Message;
            string innerMessage = "";
            if (ex.InnerException != null)
                innerMessage = ex.InnerException.Message;

            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            LogWriter.Log.Error(message, innerMessage, methName, stack);

        }
        public void Error(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            LogWriter.Log.Error(message, "", methName, stack);
        }

        public void Critical(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            LogWriter.Log.Critical(message, methName, stack);
        }

        public void Warning(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            LogWriter.Log.Warning(message, methName, stack);
        }
        public void Information(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            LogWriter.Log.Information(message, methName, stack);
        }
    }

    public class LogWriter : EventSource
    {
        public static readonly LogWriter Log = new LogWriter();

        [Event(1000, Message = "{0}", Level = EventLevel.Critical)]
        public void Critical(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(1000, parms);
        }
        [Event(1001, Message = "{0}", Level = EventLevel.Error)]
        public void Error(string message, string innerExceptionMessage, string method, string stack)
        {
            object[] parms = new object[] { message, innerExceptionMessage, method, stack };
            if (IsEnabled()) WriteEvent(1001, parms);
        }

        [Event(1002, Message = "{0}", Level = EventLevel.Warning)]
        public void Warning(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(1002, parms);
        }

        [Event(1003, Message = "{0}", Level = EventLevel.Informational)]
        public void Information(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(1003, parms);
        }
    }
}

Wiring your app to use SLAB:
Now that you have your EventSource classes setup. You need to wire up your EventSource classes when your application starts.
In Global.asax add the following ‘using’ statements:

using Microsoft.Practices.EnterpriseLibrary.SemanticLogging;
using System.Diagnostics.Tracing;

and add the bold line below to your Application_Start function (in Global.asax):

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            SetupSemanticLoggingApplicationBlock();

        }

Then add this function to your Global.asax:

protected void SetupSemanticLoggingApplicationBlock()
        {
            //EventTracing setup
            string logConnString =
                System.Configuration.ConfigurationManager.ConnectionStrings["LoggingConnString"].ToString();

            var sqlListener1 = SqlDatabaseLog.CreateListener("UI", logConnString);
            var sqlListener2 = SqlDatabaseLog.CreateListener("Infrastructure", logConnString);
            var sqlListener3 = SqlDatabaseLog.CreateListener("Service", logConnString);

            //get web.config value for logging level
            bool critcal = System.Configuration.ConfigurationManager.AppSettings["Logging_LogCritical"].ToLower() ==
                           "true";
            bool error = System.Configuration.ConfigurationManager.AppSettings["Logging_LogError"].ToLower() ==
                           "true";
            bool warning = System.Configuration.ConfigurationManager.AppSettings["Logging_LogWarning"].ToLower() ==
                           "true";
            bool info = System.Configuration.ConfigurationManager.AppSettings["Logging_LogInformation"].ToLower() ==
                          "true";

            //Enable the level of logging based on settings in web.config
            if (critcal)
            {
                sqlListener1.EnableEvents(LogWriter.Log, EventLevel.Critical);
                //uncomment if you add logging to your sub projects
                //sqlListener2.EnableEvents(MySolution.Infrastructure.InfrLogger.Log, EventLevel.Critical);
                //sqlListener3.EnableEvents(MySolution.Service.SvcLogger.Log, EventLevel.Critical);
            }
            if (error)
            {
                sqlListener1.EnableEvents(LogWriter.Log, EventLevel.Error);
                //uncomment if you add logging to your sub projects  
                //sqlListener2.EnableEvents(MySolution.Infrastructure.InfrLogger.Log, EventLevel.Error);
                //sqlListener3.EnableEvents(MySolution.Service.SvcLogger.Log, EventLevel.Error);
            }
            if (warning)
            {
                sqlListener1.EnableEvents(LogWriter.Log, EventLevel.Warning);
                //uncomment if you add logging to your sub projects                
                //sqlListener2.EnableEvents(MySolution.Infrastructure.InfrLogger.Log, EventLevel.Warning);
                //sqlListener3.EnableEvents(MySolution.Service.SvcLogger.Log, EventLevel.Warning);
            }
            if (info)
            {
                sqlListener1.EnableEvents(LogWriter.Log, EventLevel.Informational);
                //sqlListener2.EnableEvents(MySolution.Infrastructure.InfrLogger.Log, EventLevel.Informational);
                //sqlListener3.EnableEvents(MySolution.Service.SvcLogger.Log, EventLevel.Informational);
            }
        }

Adding EventSource classes in sub projects:
In all of your other projects that you include in this main project you can also have logging by adding the following file into the Lib folder (or any folder really) of those projects. You just have to make sure the name of the class is unique in each project so I will name mine SvcLogger, InfrLogger, etc, and you need to give unique codes on the Events of each (where above they are 100x, these are 200x, and if added to another project you would need to make those 300x.

So here is the sample from my Service project logger. This is all I have to add to the project to do logging in it:

using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;

namespace MySolution.Service
{
    public class SvcLogger : EventSource
    {
        public static readonly SvcLogger Log = new SvcLogger();

        public void Error(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            DoError(message, methName, stack);
        }

        public void Critical(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            DoCritical(message, methName, stack);
        }

        public void Warning(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            DoWarning(message, methName, stack);
        }

        public void Information(string message)
        {
            StackTrace st = new StackTrace();
            string methName = st.GetFrame(1).GetMethod().Name;
            string stack = st.ToString();
            DoInformation(message, methName, stack);
        }

        [Event(2000, Message = "{0}", Level = EventLevel.Critical)]
        private void DoCritical(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(2000, parms);
        }
        [Event(2001, Message = "{0}", Level = EventLevel.Error)]
        private void DoError(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(2001, parms);
        }

        [Event(2002, Message = "{0}", Level = EventLevel.Warning)]
        private void DoWarning(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(2002, parms);
        }

        [Event(2003, Message = "{0}", Level = EventLevel.Informational)]
        private void DoInformation(string message, string method, string stack)
        {
            object[] parms = new object[] { message, method, stack };
            if (IsEnabled()) WriteEvent(2003, parms);
        }

    }
}

Setup your database:
You will need to add the Traces table to your database. You will find a script in your [Solution Folder]\packages\EnterpriseLibrary.SemanticLogging.Database.2.0.1406.1\scripts folder called ‘CreateSemanticLoggingDatabaseObjects.sql’. Run this script against the database you wish to record your logs in.

Setup your app to point to your database:
In the function we put in global.asax, we specified our connection string as

 string logConnString =
                System.Configuration.ConfigurationManager.ConnectionStrings["LoggingConnString"].ToString();

Now you need to add that connection string to your web.config in the connection string section. Add:

 
    add name="LoggingConnString" connectionString="Data Source=(local);initial catalog=MyDatabase;Integrated Security=True" providerName="System.Data.SqlClient"
   

A note about error handling:
In every project except the UI (top level), I normally will want to log the error then have that error thrown again up to the calling function. To make sure that I send the call stack history, I will do it as follows:

            catch (Exception ex)
            {
                SvcLogger.Log.Error(ex.Message);
                throw;  //NOT throw ex; 
            }

instead of the following because the following won’t pass the existing stack information.

            catch (Exception ex)
            {
                SvcLogger.Log.Error(ex.Message);
                throw ex;  //this throws as if the error starts now.
            }

In the UI level I would do the following in order to make sure that sensitive info is never shown on the UI. If you are debugging this isn’t very helpful, but is also not helpful for the hacker trying to gain insight into your code.

            catch (Exception ex)
            {
                Logger.Log.Error(ex.Message);
                throw new Exception("Error deleting item. Review logs for info."); //don't pass sensitive info
            }

You will also need to set the level of errors to log in your web.config file app settings, if you set any item, then that item and the ones more critical will all be logged. For instance if I set true on ‘Warning’, then Warning, Error and Critical would be logged (as it is set in sample below). Information level items would not be logged.

 <!--######### Semantic Logging settings #########-->
    <!-- If you enable any level of Logging, it will enable that level and any level more severe. Enabling 'Warning' would enable 'Critical' and 'Error' automatically. -->
    <add key="Logging_LogCritical" value="False" />
    <add key="Logging_LogError" value="False" />
    <add key="Logging_LogWarning" value="True" /> 
    <add key="Logging_LogInformation" value="False" />
    <!--######### end Semantic Logging settings #########-->

That should get you going with an easy to setup and manage Logging system in your application. Enjoy.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s