Archive for the ‘log4net’ Tag

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.
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:

   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" />

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