Building a MVC2 Template, Part 14, Logging Services

No Comments

One of my goals with the Nehemiah Project is to make sure the ASP.Net MVC 2 Application template is modular and extensible. What happens when MVC 3 is released? We certainly don’t want to lose all our hard work. What happens if you want to use a new or different ORM? The template should allow the user to easily replace existing components and add new components in either the template or the solution built from the template.

This may be very basic for many readers, so you can jump straight to the code below if you wish. If you know the how and why of interfaces, services, and dependency injection, by all means jump ahead.

Let’s begin by breaking down the parts of the Model-View-Controller framework. The Model represents our objects that are manipulated within our code. The View represents how our models are viewed. The Controller is the traffic cop that directs the flow of the models. So what manipulates our models?

You’re most likely no more than one person removed from a developer who wrote or maintained a VB6 application. VB6 presented a nasty paradigm where the developer would drag a button to a form and then put all the code in the OnClick event of the button. Create another form that performed a similar task. Copy and paste the code from the first form. Maybe the developer tweaked the code just a bit, adding a new feature or two.  Do this six or seven times and now you eight versions of the same code spread across your application. What do you do when you need to update all of those same pieces of code?

To get out of this maintenance nightmare you might consolidate all that code into a set of common classes. So by the time you were done, the OnClick event didn’t do much more that call a method in your database class or your email class or whatever class you had created. Any code in the OnClick event was truly unique to that event.

We don’t want this kind of mess in our controllers when it comes to manipulating our models. We want a nice clean (and well defined) set of methods for anything that will modify or use our models. This anything is referred to as a service, and can be such items as our data repository, email system, membership provider, and payment processor. The set of methods exposed by the service is the interface.

For example, in our application will want a service that will log various pieces of information. It will be used in every part of our application. First we’ll start by analyzing the needs of our application to create a list of methods the service should support now and in the future. In doing this for our logging example, we can arrive at a list like this:

  • Information
  • Warning
  • Debug
  • Error
  • Fatal Error

Next we’ll formalize this list into an interface. Then we’ll write code that implements that interface. You can write your own or use an existing library. In this post I will actually implement two logging services.  You can choose to implement one or both. In the next post I’ll show how to easily switch between the two.

Now to the Code

We’re going to create a logging service using log4net and NLog. So visit each site and download the zips. For log4net I placed the binaries from log4net-1.2.10/bin/net into the folder C:\_CodeVault\log4net\1.2.10.  There are DLLs for the .Net framework versions 1.0, 1.1, and 2.0. NLog will install into the Program Files folder.  The DLLs in the installed bin folder can be copied to C:\_CodeVault\NLog\1.0.

SolutionExplorer14_01
Ok, lets add the code to our project. Create a new Class Library project named Service.Logging. Then in the project create a folder named Log4net and one named NLog. Next add references to the log4net and NLog DLLs.

ILogger Interface

Let’s begin by creating our interface as shown below.  It’s fairly simple and I borrowed a small part from Rob Conery (see references below).

using System;

namespace Service.Logging
{
    public interface ILogger
    {
        void Info(string message);
        void Warn(string message);
        void Debug(string message);
        void Error(string message);
        void Error(Exception ex);
        void Fatal(string message);
        void Fatal(Exception ex);

    }   // End Interface

}       // End Namespace

Rob had a good idea of passing the exception to the logger and letting the logger service decipher the exception. Thus the LogUtility code listed below.

using System;
using System.Web;

namespace Service.Logging
{
    public class LogUtility
    {

        public static string BuildExceptionMessage(Exception x)
        {

            Exception logException = x;
            if (x.InnerException != null)
            {
                logException = x.InnerException;
            }

            string strErrorMsg = Environment.NewLine + "Error in Path :" + HttpContext.Current.Request.Path;

            // Get the QueryString along with the Virtual Path
            strErrorMsg += Environment.NewLine + "Raw Url :" + HttpContext.Current.Request.RawUrl;

            // Get the error message
            strErrorMsg += Environment.NewLine + "Message :" + logException.Message;

            // Source of the message
            strErrorMsg += Environment.NewLine + "Source :" + logException.Source;

            // Stack Trace of the error
            strErrorMsg += Environment.NewLine + "Stack Trace :" + logException.StackTrace;

            // Method where the error occurred
            strErrorMsg += Environment.NewLine + "TargetSite :" + logException.TargetSite;

            return strErrorMsg;
        }

    }   // End Class

}       // End Namespace

NLog Logger Service

The first service implementation listed is the NLog version. This file is placed in the NLog folder.

using System;
using NLog;

namespace Service.Logging.NLog
{
    public class NLogLogger : ILogger
    {

        private Logger _logger;

        public NLogLogger()
        {
            _logger = LogManager.GetCurrentClassLogger();
        }

        public void Info(string message)
        {
            _logger.Info(message);
        }

        public void Warn(string message)
        {
            _logger.Warn(message);
        }

        public void Debug(string message)
        {
            _logger.Debug(message);
        }

        public void Error(string message)
        {
            _logger.Error(message);
        }
        public void Error(Exception x)
        {
            Error(LogUtility.BuildExceptionMessage(x));
        }
        public void Fatal(string message)
        {
            _logger.Fatal(message);
        }
        public void Fatal(Exception x)
        {
            Fatal(LogUtility.BuildExceptionMessage(x));
        }

    }   // End Class

}       // End Namespace

The NLog logger needs to be configured. The configuration for NLog is contained in the NLog.config file. I couldn’t get the logger to work when the file was placed in the Service.Logging project.  It would only work when the config file was placed in the main Nehemiah project, so add the file there.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
  <targets>
    <!--Useful for debugging-->
    <target name="console" xsi:type="ColoredConsole" layout="${date:format=HH\:mm\:ss}|${level}|${stacktrace}|${message}" />
    <target name="file" xsi:type="File" fileName="${basedir}\App_Data\site.log" layout="${date}: ${message}" />
    <target name="eventlog" xsi:type="EventLog" source="Nehemiah" log="Application" layout="${date}: ${message} ${stacktrace}" />
  </targets>
  <rules>
    <logger name="*" minlevel="Info" writeTo="file" />
    <logger name="*" minlevel="Fatal" writeTo="file" />
  </rules>
</nlog>

When you read the NLog.config file you will notice that NLog creates targets for the log messages. In this config file are console, a file, and the event log.  For the file logger the file site.log is created in the App_Data folder of the application. In the rules section are defined which target is used based upon the error level.  For our purposes everything is written to the site.log file.

Log4Net Logger Service

The log4net logger service is very similar to the NLog service as you can see below.

using System;
using log4net;

namespace Service.Logging.Log4Net
{

    public class Log4NetLogger : ILogger
    {

        private static ILog log;

        public Log4NetLogger()
        {
            log = LogManager.GetLogger(typeof(Log4NetLogger));
            log4net.Config.XmlConfigurator.Configure();
        }

        #region ILogger Members

        public void Info(string message)
        {
            log.Info(message);
        }

        public void Warn(string message)
        {
            log.Warn(message);
        }

        public void Debug(string message)
        {
            log.Debug(message);
        }

        public void Error(string message)
        {
            log.Error(message);
        }

        public void Error(Exception x)
        {
            Error(LogUtility.BuildExceptionMessage(x));
        }

        public void Fatal(string message)
        {
            log.Fatal(message);
        }

        public void Fatal(Exception x)
        {
            Fatal(LogUtility.BuildExceptionMessage(x));
        }

        #endregion

    }   // End Class

}       // End Namespace

The log4net configuration is added to the web.config file in the main Nehemiah project. Add the following line to the configSections.

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net" />

Next, add the following XML after the section beginning with <location path=”elmah.axd”>.

  <log4net>
    <root>
      <level value="DEBUG" />
      <appender-ref ref="LogFileAppender" />
    </root>
    <appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender" >
      <param name="File" value="App_Data\log.txt" />
      <param name="AppendToFile" value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n" />
      </layout>
    </appender>
  </log4net>

Next Time

In the next post we’ll add our logging service to the template using Ninject.

References

Previous Articles in this Series

Shout it


Kick It on DotNetKicks.com
Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)

Leave a Reply

Comment moderation is enabled. Your comment may take some time to appear.