Writing An Appender For log4net

In log4net speak, an appender is an output destination for a log such as a file, the console, a database or even email.  log4net ships with so many appenders that most of us will never need to write our own.  There are cases where you may find a need for your own appender, for example, you may want to log errors to your company’s bug tracking software. 

In our case, we simply wanted error logs to pop up a message box with the error and location.  We run this internally so that developers run into errors immediately during development and can break into the debugger to fix them.  We found that logging to a file was too easy to ignore.

MessageBoxAppender

I was pleasantly surprised how easy it is to write a new appender, but there is very little information on the web, so I thought it would be best to give an example.

  1. Create a new Class Library project in Visual Studio.
  2. Add a reference to log4net. My appender also uses MessageBox, so I also added references to System.Drawing and System.Windows.Forms.
  3. Remove the default Class1.cs added to the project.
  4. Add your appender class. In my case, MessageBoxAppender.cs.
  5. You could implement the log4net.Appender.IAppender interface, but it is easiest to derive from log4net.Appender.AppenderSkeleton, then most of the work is done for you.
  6. At a minimum, override the Append method. This is where you do your work.
  7. If you are going to use the RenderLoggingEvent method to create your logging message based on the configured layout (such as PatternLayout), override the RequiresLayout property and return true.
  8. When you configure your appender, you must give the assembly qualified name for your appender.  For example,

<appender name=”…” type=”MyNamespace.MyAppender, MyAssembly”>

Here is the simplified code for the MessageBoxAppender that I wrote.

<span style="color: rgb(0,0,255)">using</span> System;
<span style="color: rgb(0,0,255)">using</span> System<span style="color: rgb(255,0,0)">.</span>Windows<span style="color: rgb(255,0,0)">.</span>Forms;
<span style="color: rgb(0,0,255)">using</span> System<span style="color: rgb(255,0,0)">.</span>Diagnostics;

<span style="color: rgb(0,0,255)">using</span> log4net<span style="color: rgb(255,0,0)">.</span>Core;
<span style="color: rgb(0,0,255)">using</span> log4net<span style="color: rgb(255,0,0)">.</span>Appender;
<span style="color: rgb(0,0,255)">
namespace</span> Alteridem<span style="color: rgb(255,0,0)">.</span>log4net
{
   <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> </span><span style="color: rgb(128,128,128)">&lt;summary&gt;
</span>   <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> Displays a MessageBox for all log messages.
</span>   <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> </span><span style="color: rgb(128,128,128)">&lt;/summary&gt;
</span>   <span style="color: rgb(0,0,255)">public</span> <span style="color: rgb(0,0,255)">class</span> <span style="color: rgb(43,145,175)">MessageBoxAppender</span> : <span style="color: rgb(43,145,175)">AppenderSkeleton
</span>   {
      <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> </span><span style="color: rgb(128,128,128)">&lt;summary&gt;
</span>      <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> Writes the logging event to a MessageBox
</span>      <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> </span><span style="color: rgb(128,128,128)">&lt;/summary&gt;</span>
<span style="color: rgb(0,0,255)">      override</span> <span style="color: rgb(0,0,255)">protected</span> <span style="color: rgb(0,0,255)">void</span> Append( <span style="color: rgb(43,145,175)">LoggingEvent</span> loggingEvent )
      {
         <span style="color: rgb(0,0,255)">string</span> title <span style="color: rgb(255,0,0)">=</span> <span style="color: rgb(0,0,255)">string</span><span style="color: rgb(255,0,0)">.</span>Format( <span style="color: rgb(163,21,21)">"{0} {1}"</span>, 
            loggingEvent<span style="color: rgb(255,0,0)">.</span>Level<span style="color: rgb(255,0,0)">.</span>DisplayName, 
            loggingEvent<span style="color: rgb(255,0,0)">.</span>LoggerName );

         <span style="color: rgb(0,0,255)">string</span> message <span style="color: rgb(255,0,0)">=</span> <span style="color: rgb(0,0,255)">string</span><span style="color: rgb(255,0,0)">.</span>Format( 
            <span style="color: rgb(163,21,21)">"{0}{1}{1}{2}{1}{1}(Yes to continue, No to debug)"</span>, 
            RenderLoggingEvent( loggingEvent ), 
            <span style="color: rgb(43,145,175)">Environment</span><span style="color: rgb(255,0,0)">.</span>NewLine, 
            loggingEvent<span style="color: rgb(255,0,0)">.</span>LocationInformation<span style="color: rgb(255,0,0)">.</span>FullInfo );

         <span style="color: rgb(43,145,175)">DialogResult</span> result <span style="color: rgb(255,0,0)">=</span> <span style="color: rgb(43,145,175)">MessageBox</span><span style="color: rgb(255,0,0)">.</span>Show( message, title, 
            <span style="color: rgb(43,145,175)">MessageBoxButtons</span><span style="color: rgb(255,0,0)">.</span>YesNo );
         
         <span style="color: rgb(0,0,255)">if</span> ( result <span style="color: rgb(255,0,0)">==</span> <span style="color: rgb(43,145,175)">DialogResult</span><span style="color: rgb(255,0,0)">.</span>No )
         {
            <span style="color: rgb(43,145,175)">Debugger</span><span style="color: rgb(255,0,0)">.</span>Break();
         }
      }
      <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> </span><span style="color: rgb(128,128,128)">&lt;summary&gt;
</span>      <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> This appender requires a </span><span style="color: rgb(128,128,128)">&lt;see cref="Layout"/&gt;</span><span style="color: rgb(0,128,0)"> to be set.
</span>      <span style="color: rgb(128,128,128)">///</span><span style="color: rgb(0,128,0)"> </span><span style="color: rgb(128,128,128)">&lt;/summary&gt;
</span>      <span style="color: rgb(0,0,255)">override</span> <span style="color: rgb(0,0,255)">protected</span> <span style="color: rgb(0,0,255)">bool</span> RequiresLayout
      {
         <span style="color: rgb(0,0,255)">get</span> { <span style="color: rgb(0,0,255)">return</span> <span style="color: rgb(0,0,255)">true</span>; }
      }
   }
}

There isn’t much to see here. I use the log level and the log name in the titlebar. I display the rendered message string and the calling location in the MessageBox and I break into the debugger if you press No.

Now, to configure this for your developers to see ERROR messages, the following configuration would work.

<span style="color: rgb(0,0,255)">&lt;?</span><span style="color: rgb(163,21,21)">xml</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">version</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">1.0</span>"<span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">encoding</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">utf-8</span>"<span style="color: rgb(0,0,255)"> ?&gt;
&lt;</span><span style="color: rgb(163,21,21)">configuration</span><span style="color: rgb(0,0,255)">&gt;
   &lt;</span><span style="color: rgb(163,21,21)">configSections</span><span style="color: rgb(0,0,255)">&gt;
      &lt;</span><span style="color: rgb(163,21,21)">section</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">name</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">log4net</span>"
<span style="color: rgb(0,0,255)">        </span><span style="color: rgb(255,0,0)">type</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">log4net.Config.Log4NetConfigurationSectionHandler, log4net</span>"<span style="color: rgb(0,0,255)"> /&gt;
   &lt;/</span><span style="color: rgb(163,21,21)">configSections</span><span style="color: rgb(0,0,255)">&gt;
   &lt;</span><span style="color: rgb(163,21,21)">log4net</span><span style="color: rgb(0,0,255)">&gt;
      &lt;</span><span style="color: rgb(163,21,21)">appender</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">name</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">MessageBoxAppender</span>"<span style="color: rgb(0,0,255)"></span>
            <span style="color: rgb(255,0,0)">type</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">Alertidem.log4net.MessageBoxAppender, log4netExtensions</span>"<span style="color: rgb(0,0,255)">&gt;
         &lt;</span><span style="color: rgb(163,21,21)">layout</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">type</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">log4net.Layout.PatternLayout</span>"<span style="color: rgb(0,0,255)">&gt;
            &lt;</span><span style="color: rgb(163,21,21)">ConversionPattern</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">value</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">%m</span>"<span style="color: rgb(0,0,255)"> /&gt;
         &lt;/</span><span style="color: rgb(163,21,21)">layout</span><span style="color: rgb(0,0,255)">&gt;         
      &lt;/</span><span style="color: rgb(163,21,21)">appender</span><span style="color: rgb(0,0,255)">&gt;
      &lt;</span><span style="color: rgb(163,21,21)">root</span><span style="color: rgb(0,0,255)">&gt;
         &lt;</span><span style="color: rgb(163,21,21)">level</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">value</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">ERROR</span>"<span style="color: rgb(0,0,255)">/&gt;
         &lt;</span><span style="color: rgb(163,21,21)">appender-ref</span><span style="color: rgb(0,0,255)"> </span><span style="color: rgb(255,0,0)">ref</span><span style="color: rgb(0,0,255)">=</span>"<span style="color: rgb(0,0,255)">MessageBoxAppender</span>"<span style="color: rgb(0,0,255)"> /&gt;
      &lt;/</span><span style="color: rgb(163,21,21)">root</span><span style="color: rgb(0,0,255)">&gt;
   &lt;/</span><span style="color: rgb(163,21,21)">log4net</span><span style="color: rgb(0,0,255)">&gt;
&lt;/</span><span style="color: rgb(163,21,21)">configuration</span><span style="color: rgb(0,0,255)">&gt;</span>

The only thing to not here is that the log4netExtensions in the appender line is the assembly.

This should be enough to give you a basic framework to build whatever type of appender you want.  Every time I delve into a new area of log4net, I am once again surprised how easy it is to work with and how well designed it is.  If you aren’t using it, I would highly recommend it.

8 thoughts on “Writing An Appender For log4net

  1. The only thing that I can think of that would be thread safe is to put the result in thread local storage in the appender and then retrieve it in your code after the logging call. You will need to make sure you handle if it is not found in thread local storage because the logging configuration may change. Also, if you have a lot of logging, it could be problematic to handle it everywhere.

    You might also be able to throw a specific exception in the appender and catch it in your code.

    Personally, both of these solutions feel wrong to me.

  2. hi ! thank you for this useful code :)
    I would like to know if you have a trick to retrieve the DialogResult in my app after the call of Append method ?

    Thanks !

  3. I am lucky to find this webpage. i implemented this custom appender successfully with this article. thank you very much for this good and complete inforamtion

  4. Rambhopal,

    I expect that you either haven’t set up logging in your application, or your configuration is incorrect.

    1. If you change your configuration to log to a file, does that work? If not, then you need to set up logging in your application. You either have to add an XmlConfigurator attribute to your assembly, or call XmlConfigurator.ConfigureAndWatch( ConfigFile ); See the log4net docs on getting started, or my presentation example code at http://www.alteridem.net/2008/02/29/log4net-slides-and-example-code/

    2. If logging to file is working, then you probably have it configured wrong. Make sure the first part of type in the appender tag matches your appender name and make sure the second part matches the DLL or EXE name that the appender is in. Check this line;

    Let me know if you have problems.

    Rob

  5. It’s very helpful to me. I didn’t think it’s so easy to do. I used this as I wrote appender to Wcf. Thank you!

Comments are closed.