How to write custom ADO.NET APPENDER for LOG4NET
Log4NET is the best thing out there to write data to variety of targets. It provides so many ways to...
Typically when you invoke Info, Error or Debug methods of the Logger, Log4Net internally invokes Render method of the DefaultRender to render objects. Log4Net logging methods not only accept string but also an object as parameter. When you pass an object to the method, the default render calls the ToString() on the object parameter and writes the content to the appender source. The default renderer supports rendering objects to strings as follows:
| Value | Rendered String |
| null | “(null)” |
| Array | For a one dimensional array this is the array type name, an open brace, followed by a comma separated list of the elements (using the appropriate renderer), followed by a close brace.
For example: int[] {1, 2, 3}. If the array is not one dimensional the Array.ToString() is returned. |
| IEnumerable, ICollection & IEnumerator | Rendered as an open brace, followed by a comma separated list of the elements (using the appropriate renderer), followed by a close brace. For example: {a, b, c}. All collection classes that implement ICollection its subclasses, or generic equivalents all implement the IEnumerable interface. |
| DictionaryEntry | Rendered as the key, an equals sign (‘=’), and the value (using the appropriate renderer). For example: key=value. |
| other | Object.ToString() |
If an error occurred in the code and if you pass the exception object to the logger methods, the default render transforms it into string before passing it to all appenders. It would typically log error information such as type of exception, message & its stack trace. This may be not be enough for all the scenarios. In some cases, we might need additional information and such as TargetSite, Source etc. It could be that the object is not just a System.Exception but your own custom exception where you also record the request related information which you would like to be logged. The answer to this problem is to write our own Custom Renderer and have it registered with Log4Net. When Log4Net come across this object, it will call our custom renderer. In the custom renderer class, we can format the message the way we want to and write it to the renderer (TextWriter).
If you are new to Log4Net, I would recommend you read these below articles before you proceed further.
Understanding LOG4NET configuration
Log to a file using LOG4NET Rolling File Appender
Log to database using LOG4NET Ado.Net Appender
Create a custom exception class “MyException” extending from ApplicationException class. Here we store additional information such as Transaction ID & Username in addition to the base exception.
public class MyException : ApplicationException
{
public string TransID;
public string Username;
}
Create a new class CustomExceptionRender and have it extended from log4net.ObjectRenderer.IObjectRenderer interface. All we have to do is override the “RenderObject” method and write our own rendering logic. In the render method, we start by writing transaction id & username to the textwriter. Then we continue writing basic exception information such as exception message, source, type etc. If any additional information is added to the DATA dictionary, flush even that information to the writer. We apply the same logic to any inner exceptions of the base exception object.
public class CustomExcpetionRenderer : IObjectRenderer
{
public void RenderObject(RendererMap rendererMap, object obj,
TextWriter writer)
{
MyException myException = obj as MyException;
writer.WriteLine(string.Format("Transaction ID : {0}",
myException.TransID));
writer.WriteLine(string.Format("Username : {0}",
myException.Username));
Exception exception = obj as Exception;
while (exception != null)
{
WriteException(exception, writer);
exception = exception.InnerException;
}
}
private void WriteException(Exception ex, TextWriter writer)
{
writer.WriteLine(string.Format("Type: {0}", ex.GetType().FullName));
writer.WriteLine(string.Format("Message: {0}", ex.Message));
writer.WriteLine(string.Format("Source: {0}", ex.Source));
writer.WriteLine(string.Format("TargetSite: {0}", ex.TargetSite));
WriteExceptionData(ex, writer);
writer.WriteLine(string.Format("StackTrace: {0}", ex.StackTrace));
}
private void WriteExceptionData(Exception ex, TextWriter writer)
{
foreach (DictionaryEntry entry in ex.Data)
{
writer.WriteLine(string.Format("{0}: {1}", entry.Key,
entry.Value));
}
}
}
Now we can go ahead and register the class in the configuration file as shown below:
<renderer renderingClass="TestConsoleApp.CustomExcpetionRenderer" renderedClass="TestConsoleApp.MyException" />
For our example, I am using Rolling File appender and writing contents to a log file. Copy following Log4Net configuration to your application configuration file (app.config)
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
</configSections>
<log4net>
<root>
<level value="DEBUG" />
<appender-ref ref="MyRollingFileAppender" />
</root>
<appender name="MyRollingFileAppender"
type="log4net.Appender.RollingFileAppender">
<param name="File" value="C:\Temp\log.txt" />
<appendToFile value="true" />
<maximumFileSize value="10KB" />
<maxSizeRollBackups value="5" />
<rollingStyle value="Size" />
<threshold value="DEBUG" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %logger %newline%message" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG" />
<levelMax value="ERROR" />
</filter>
</appender>
<renderer renderingClass="TestConsoleApp.CustomExcpetionRenderer"
renderedClass="TestConsoleApp.MyException" />
</log4net>
</configuration>
In order to test this solution, let’s write a simple console application. In the Main method, we prepare our custom exception object and populate its properties. Finally we pass this object to the Error method of the logger and will Log4Net will do the rest.
public class Program
{
static void Main(string[] args)
{
ILog _log =
LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
try
{
MyException exp = new MyException();
exp.TransID = System.Guid.NewGuid().ToString();
exp.Username = "testuser";
exp.Data.Add("key1", "value1");
exp.Data.Add("key2", "value2");
throw exp;
}
catch (MyException myExp)
{
_log.Error(myExp);
}
}
Run the application. When it prompts you to close the console window, go ahead and close it. Open C:\Temp\Log.txt file and you would notice that our custom information is written to the log.