Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Redirect LINQ to SQL DataContext Log Messages To Log4Net

4.23/5 (12 votes)
14 May 2008CPOL3 min read 1   294  
An article that shows how one can use Log4Net to capture the log messages that are generated by the LINQ to SQL DataContext classes

Introduction

Visual Studio automatically generates the subclasses of DataContext class when dealing with LINQ to SQL for ORM. These auto generated classes have a property named Log of type System.IO.TextWriter. If an object of type TextWriter or its subclass is assigned to this property, then it will use that instance for writing the log messages. As Log4Net is one of the most used Logging libraries for .NET enterprise level projects, in this project I will present a solution to capture the log messages through Log4Net logging.

Background

In my project, I have a well established logging framework that uses Log4Net. Now that I moved to LINQ to SQL, I needed to get the raw SQL that is generated from the LINQ C# codes for both instrumentation and performance purposes. From MSDN, I came to know that I need to pass an instance of type TextWriter to get the log messages from the DataContext subclasses. So, I ended up writing a subclass of TextWriter called LINQLogger, which encapsulates the responsibility of using Log4Net logger under the hood as it receives the log messages from the DataContext classes.

I hope that the readers of this article with a similar requirement may find it useful in their projects as well.

Using the Code

I will be on code-description-code-description mode in this article and discuss the key parts of the sample code. Please click the download link at the top for the full source code of the sample application that I wrote for this article.

The Class Diagram

The source code contains a few interfaces and classes. The class diagram of the sample application is as follows:

Image 1

The ILogger Interface

I have created an interface with the methods that I use for logging. This interface allows me to extract the dependency on Log4Net or whatever logging framework I am using. This simple interface is presented in the following code segment:

C#
namespace LINQ2Log4Net
{
    // An interface for loggers.
    public interface ILogger
    {
        // This method creates a logger for specified class
        void CreateLogger(Type loggerType);

        // Indexer for setting properties for logging
        string this[string propertyName]
        {
            set;
        }

        //These methods log in various logging levels
        void Debug(string message);
        void Fatal(string message);
        void Info(string message);
        void Error(string message);
        void Warn(string message);
    }
}

The Log4NetLogger : ILogger Class

Log4NetLogger is an implementation of the interface ILogger that internally uses Log4Net for logging. I have the following code in my class for logging:

C#
 namespace LINQ2Log4Net
{
    /// <summary>
    /// This is an implementation of ILogger interface which uses log4net for logging.
    /// </summary>
    public class Log4NetLogger : ILogger
    {
        private ILog log;

        public void CreateLogger(Type loggerType)
        {
            log = LogManager.GetLogger(loggerType);
        }

        // An implementation of interface indexer for setting logging properties.
        public string this[string propertyName]
        {
            set { ThreadContext.Properties[propertyName] = value; }
        }

        // An implementation of interface method for logging Debug messages
        public void Debug(string message)
        {
            if (log.IsDebugEnabled)
                log.Debug(message);
        }

        // An implementation of interface method for logging Fatal messages
        public void Fatal(string message)
        {
            if (log.IsFatalEnabled)
                log.Fatal(message);
        }

        // An implementation of interface method for logging Info messages
        public void Info(string message)
        {
            if (log.IsInfoEnabled)
                log.Info(message);
        }

        // An implementation of interface method for logging Error messages
        public void Error(string message)
        {
            if (log.IsErrorEnabled)
                log.Error(message);
        }

        // An implementation of interface method for logging Warning messages
        public void Warn(string message)
        {
            if (log.IsWarnEnabled)
                log.Warn(message);
        }
    }
}

The LINQLogger : System.IO.TextWriter Class

As I have already mentioned in the introduction, the DataContext class's Log property is of type TextWriter and we need a subclass of TextWriter.LINQLogger. Our subclass called LINQLogger internally holds a reference to a ILogger object and when the Write method is invoked from the DataContext object, it invokes the appropriate method on the logger to perform the logging as shown in the next code fragment:

C#
 namespace LINQ2Log4Net
{
    public class LINQLogger : TextWriter
    {
        //The ILogger Instance
        private ILogger _logger;
        public ILogger Logger
        {
            get { return _logger; }
            set { _logger = value; }
        }
        //The Log level enum
        private LogLevelType _logLevelType;
        public LogLevelType LogLevel
        {
            get { return _logLevelType; }
            set { _logLevelType = value; }
        }

        private Encoding _encoding;
        public override Encoding Encoding
        {
            get
            {
                if (_encoding == null)
                {
                    _encoding = new UnicodeEncoding(false, false);
                }
                return _encoding;
            }
        }

        public LINQLogger()
        {

        }

        public LINQLogger(ILogger logger, LogLevelType logLevel)
        {
            _logger = logger;
            _logLevelType = logLevel;
        }

        public override void Write(string value)
        {
            switch (_logLevelType)
            {
                case LogLevelType.Fatal:    _logger.Fatal(value);   break;
                case LogLevelType.Error:    _logger.Error(value);   break;
                case LogLevelType.Warn:     _logger.Warn(value);    break;
                case LogLevelType.Info:     _logger.Info(value);    break;
                case LogLevelType.Debug:    _logger.Debug(value);   break;
            }
        }

        public override void Write(char[] buffer, int index, int count)
        {
            Write(new string(buffer, index, count));
        }
    }

    public enum LogLevelType
    {
        Fatal,
        Error,
        Warn,
        Info,
        Debug
    }
}

The SampleDBDataContext and the Visitor Classes

These two classes are auto generated by Visual Studio 2008 and so I am skipping the source code. However, from the class diagram, you can see the properties of the Visitor class which we will be using for demonstration purposes here.

The Example Use

In the following code fragment, you will see an example use scenario of the classes that I have discussed up to now. The key flow is as follows:

  • Initialize the Log4NetLogger
  • Initialize the LINQLogger with the Log4NetLogger and the preferred LogLevelType
  • Assign the LINQLogger object to the SampleDataContext's Log property
  • See the logging in action by invoking a few LINQ to SQL queries
C#
 //Initialize the Log4Net from the XML
log4net.Config.XmlConfigurator.Configure();

//Create the logger for this class
ILogger logger = new Log4NetLogger();
logger.CreateLogger(typeof(LINQ2Log4Net.Program));

//Create an instance of the LINQLogger
LINQLogger linqLogger = new LINQLogger(logger, LogLevelType.Debug);

//Set the linqLogger to the log of the DataContext
SampleDBDataContext context = new SampleDBDataContext();
context.Log = linqLogger;

//Run some queries in the context
Visitor visitor = new Visitor { Name = "S M Sohan", Country = "Bangladesh" };
context.Visitors.InsertOnSubmit(visitor);
context.SubmitChanges();

int count = (from v in context.Visitors select v).Count();
Console.WriteLine("Total # of visitors = {0}", count);

Disclaimer

The whole intention behind this article is to show you the way to achieve Log4Net logging with LINQ to SQL classes. So, I have intentionally avoided other aspects like exception handling codes to focus on the target spot only.

History

  • 14th May, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)