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:
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:
namespace LINQ2Log4Net
{
public interface ILogger
{
void CreateLogger(Type loggerType);
string this[string propertyName]
{
set;
}
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:
namespace LINQ2Log4Net
{
public class Log4NetLogger : ILogger
{
private ILog log;
public void CreateLogger(Type loggerType)
{
log = LogManager.GetLogger(loggerType);
}
public string this[string propertyName]
{
set { ThreadContext.Properties[propertyName] = value; }
}
public void Debug(string message)
{
if (log.IsDebugEnabled)
log.Debug(message);
}
public void Fatal(string message)
{
if (log.IsFatalEnabled)
log.Fatal(message);
}
public void Info(string message)
{
if (log.IsInfoEnabled)
log.Info(message);
}
public void Error(string message)
{
if (log.IsErrorEnabled)
log.Error(message);
}
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:
namespace LINQ2Log4Net
{
public class LINQLogger : TextWriter
{
private ILogger _logger;
public ILogger Logger
{
get { return _logger; }
set { _logger = value; }
}
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
log4net.Config.XmlConfigurator.Configure();
ILogger logger = new Log4NetLogger();
logger.CreateLogger(typeof(LINQ2Log4Net.Program));
LINQLogger linqLogger = new LINQLogger(logger, LogLevelType.Debug);
SampleDBDataContext context = new SampleDBDataContext();
context.Log = linqLogger;
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