Writing a Custom Appender for log4net

On most of my projects, I use log4net (a habit I carried over from Java really) to maintain traces and logs. I find it preferable to using the trace mechanism, and it certainly beats Console.Write as a technique. One of the best things about it is that you can configure appenders to write to pretty much anything. The appenders that come bundled with log4net cover most of the stuff you’d actually want to log to – the event logs, console, a database, file system, and so on. However, sometimes you may need something a little more specific.

In my case, I needed to have one of my projects log issues directly into the issue tracking system we use here. Said tracking system exposes a web service to do just that, so I set about writing an appender that would consume this service. I’m not going to go into the details of how to connect to this specific system, but hopefully this short post will illustrate how incredibly easy it is to write a custom appender.

The best place to start off is by extending log4net.Appender.AppenderSkeleton. This will provide 99% of the functionality needed to make an appender work. Depending on your specific needs, you might find something even closer to your target to work from, but this was fine for me.

AppenderSkeleton provides two abstract overloads of the Append method. This is what the framework calls to let the appender know there is something to log. All I needed was to convert the log message that is passed to this method to a string. To keep things simple and configurable, we can use the following:

string logMessage = RenderLoggingEvent(loggingEvent);

The RenderLoggingEvent method uses the Layout provider specified in the appender configuration. This allows us to keep our appender configuration consistent with any other logger out there, with no extra effort (a subject very close to my heart. The lack of extra effort that is, not consistency).

Once the message has been prepared, we can fire it off as we would with any web service call. However, calling a web service may itself raise an exception. Logging errors should be transparent to the rest of the application, so we don’t want the exception to be thrown back to us. Instead, we can trap it and pass it to the logger’s error handler:

// Send the log message to the web service.
try
{
    Send(logMessage);
}
catch (Exception e)
{
    ErrorHandler.Error("An error occurred while connecting to the logging service.", e);
}


The error handler is, once again, determined by the configuration and would be responsible for deciding what to do with the exception. For simplicity’s sake, I only defined a handler for the generic exception type here, but it might help if you add appropriate handlers if you intend to use such code.

The example provided in the downloadable code is rather simple – the logging service it connects to only accepts one parameter. In reality, you would probably need to specify a number of properties, including authentication settings and so forth. Luckily, log4net makes it easy to keep everything configurable. Any public property defined in the appender class can be set up in the configuration file as follows:

<appender
   name="Gemini"
   type="GeminiAppender.GeminiAppender, GeminiAppender">
   <ServiceURL value="https://****.****.***/webservices/gemini.asmx" />
   <AccessCode value="********" />
   <ApplicationId value="**" />
   <ReporterId value="**" /> 
   <threshold value="ERROR" />
   <layout type="log4net.Layout.PatternLayout">
    <!-- Pattern to output the caller's file name and line number -->
    <conversionPattern value="%5level - %message%newline" />
   </layout>
  </appender>

The underlined elements are properties defined in the appender class. Note that the dll containing the appender must be in the binaries folder (not necessarily referenced directly, but it must be present).

The sample code can be downloaded here. Note that there is an issue with the test (Attempted to access unloaded AppDomain) – this seems to be something to do with the messed up way I set up cassini; it only appears if the test ran correctly, which is highly annoying. Updates and further information will be posted as soon as I figure out why it’s doing this.

kick it on DotNetKicks.com

11 Replies to “Writing a Custom Appender for log4net”

  1. Hi Kaushal, the source code can be downloaded from

    http://cid-f9403e3f2829cab9.skydrive.live.com/self.aspx/The%20Simple%20Part/writing-a-custom-appender-for-log4net/WebServiceAppender.zip

    To create a custom appender in your project, create a class that inherits from AppenderSkeleton and override the Append(LoggingEvent[] loggingEvents) and Append(LoggingEvent loggingEvent) methods. Inside these methods, you can write to MSMQ in the same way you normally would in an application. Let me know if you have any problems downloading the source code or anything.

  2. I found the code pretty useful. But, i donno how i can configure this custom appender to my project. Can you please guide me.

  3. Hi Prasad, thanks for your comment.

    The appender described here assumes that you are using a Gemini issue tracking system. To use the custom appender, place the dll generated by the project with the rest of your project binaries. Also include the log4net dlls.

    Finally, include the tags shown in the article (appender name=”Gemini” and so on) in your application .config file. The values in to use depend on your Gemini installation, so you might want to check with the documentation or the person who maintains your issue tracking system about it.

    Thanks again for commenting. Let me know if you have any problems 🙂

  4. Thanks for the reply.

    Is it possible to Override the Error,Debug…etc functions in my custom appender. so that, i can use some more custom parameters, along with message and exception.

  5. Hi Karl,

    Another great post. I’ve been looking at log4net this week and hadn’t thought to write an appender to log into our issue tracking system.

    Brilliant idea!

    Cheers,

    Ryan

    1. Thanks Ryan, please do let me know if it works out ok! The trickiest part I found with this appender was to decide what gets logged as an issue or not – uncaught exceptions being a given. Be sure to look at whatever mechanism your issue tracker uses to log duplicates too … it’s easy to get flooded otherwise 😉

Comments are closed.