Loading the assembly for a custom log4net appender

 

As I described recently, I have built a custom appender that inherits from SmtpAppender but supports SSL sending using .NET 2.0.

However, when I configure my web application to use it, I get an error that:
log4net:ERROR XmlHierarchyConfigurator: Could not create Appender [EmailAppender] of type [log4netExtensions.SmtpClientAsyncSmtpAppender]. Reported error follows.
System.TypeLoadException: Could not load type [log4netExtensions.SmtpClientAsyncSmtpAppender]. Tried assembly [log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821] and all loaded assemblies

It can't load the new appender since that assembly isn't loaded.  To specify that an appender comes from a non-log4net assembly, you add the assembly name after the appender.  For example,

<appender name="EmailAppender" type="log4netExtensions.SmtpClientAsyncSmtpAppender, log4netExtensions">

Note that you need to ensure that assembly is available, so you may need to add a reference to it in your project.

 

A log4net appender that uses SmtpClient
Google Apps provides free email hosting for small businesses.  However, their SMTP server requires SSL authentication for sending outbound emails, so you can't use use log4net to send emails based on the content of logged messages.  Ron Grabowski suggested writing a log4net appender that uses SmtpClient (only available in 2.0) to send SSL secured messages. The below class works for me against smtp.google.com using port 587:

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using log4net.Appender;

using log4net.Core;

using System.Net.Mail;

using System.Net;

namespace log4netExtensions

{

  /// <summary>

  /// The standard log4net SmtpAppender doesn't support SSL authentication, which is

  /// required to send email via gmail.

  ///

  /// This appender uses the SmtpClient (only available in .NET 2.0) to send SMTP mail that

  /// is secured via SSL.  This is needed to talk to the gmail SMTP server. 

  ///

  /// This code is heavily based on that posted by Ron Grabowski at:

  /// http://mail-archives.apache.org/mod_mbox/logging-log4net-user/200602.mbox/%3C20060216123155.22007.qmail@web32202.mail.mud.yahoo.com%3E

  /// </summary>

  public class SmtpClientSmtpAppender : SmtpAppender

  {

    override protected void SendBuffer(LoggingEvent[] events)

    {

      try

      {

        StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);

        string t = Layout.Header;

        if (t != null)

        {

          writer.Write(t);

        }

        for (int i = 0; i < events.Length; i++)

        {

          // Render the event and append the text to the buffer

          RenderLoggingEvent(writer, events[i]);

        }

        t = Layout.Footer;

        if (t != null)

        {

          writer.Write(t);

        }

        // Use SmtpClient so we can use SSL.

        SmtpClient client = new SmtpClient(SmtpHost, Port);

        client.EnableSsl = true;

        client.Credentials = new NetworkCredential(Username, Password);

        string messageText = writer.ToString();

        MailMessage mail = new MailMessage(From, To, Subject, messageText);

        client.Send(mail);

      }

      catch (Exception e)

      {

        ErrorHandler.Error("Error occurred while sending e-mail notification from SmtpClientSmtpAppender.", e);

      }

    }

  }

}

A realistic log4net config

Most log4net config file examples show the simplest case.  Here is a more realistic example of a production log4net config that uses multiple appenders.  This comes from an ASP.NET application, but the same technique will work in a server or client application.  This config sets up two appenders:

  1. The first one writes all messages at DEBUG or higher to a log file.  Depending on your needs, a RollingFileAppender that creates a new file every day or week might be more appropriate.
  2. The second appender sends email messages when a new user account is created, or when an error is logged.

<?xml version="1.0"?>

<configuration>

<configSections>

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>

</configSections>

<log4net>

<!-- The DebugFileAppender writes all messages to a log file-->

<appender name="DebugFileAppender" type="log4net.Appender.FileAppender">

<file value="LinkedCellsWebService.log" />

<threshold value="DEBUG" />

<appendToFile value="true" />

<layout type="log4net.Layout.PatternLayout">

<param name="ConversionPattern" value="%5p [%d] - %m%n" />

</layout>

</appender>

<!-- The EmailAppender sends an email when something matches the filters-->

<appender name="EmailAppender" type="log4net.Appender.SmtpAppender">

<evaluator type="log4net.Core.LevelEvaluator">

<threshold value="DEBUG"/>

</evaluator>

<!-- The filters are processed in order:

1) match the Inserted New User message

2) match any WARN or higher messages

3) reject everything else -->

<filter type="log4net.Filter.StringMatchFilter">

<stringToMatch value="Inserted a new user" />

<acceptOnMatch value="true" />

</filter>

<filter type="log4net.Filter.LevelRangeFilter">

<levelMin value="WARN" />

<acceptOnMatch value="true" />

</filter>

<filter type="log4net.Filter.DenyAllFilter" />

<!-- The SmtpAppender authenticates against the mail server, the buffersize of 10 provides 10 lines

of context when an error happens. -->

<subject value="LinkedCells: Production log event" />

<to value="notifications@LinkedCells.com" />

<from value="notifications@LinkedCells.com" />

<password value ="password" />

<smtpHost value="MAILSERVER" />

<bufferSize value="10" />

<lossy value="true" />

<layout type="log4net.Layout.PatternLayout">

<param name="ConversionPattern" value="%5p [%d] - %m%n" />

</layout>

</appender>

<root>

<!-- add other appenders here and the log messages will be sent to every listed appender -->

<appender-ref ref="DebugFileAppender" />

<appender-ref ref="EmailAppender" />

</root>

</log4net>

----- END OF CONFIG -----
Software Startup Series
My series highlighting helpful information for software startups is now in the 8th week.  I started this as a way to communicate helpful information with my partners, but it is starting to get some traffic and take on a life of its own.  The micro-ISV movement is gaining momentum, Channel 9 has even started a show about it.
Installing VS 2003 AFTER VS 2005

Like most developers, I've already upgraded to VS 2005.  However, a product which I am releasing soon uses .NET 1.1 for the client side to ease installation.  So I had to install VS 2003 after having VS 2005 installed.  Luckily, and thanks to MS, it worked great. 

Porting the client code back to 1.1 wasn't too bad.  Recreating the project files was a pain, and realizing that the DataGrid is 2.0 only was a bummer.  However, SourceGrid looks like it is going to work nicely.

 

Razr survives the washing machine

Following a long night of poker on Saturday, I started a load of laundry.  After transferring it to the dryer, I heard a suspicious thumping and found my Razr had gone through the full cycle.

I figured it was dead, but I took out the battery and the sim card and let it dry for 2 days.  After reassembly and 12 hours of charging, it turned on and displayed a lovely background of clouds.  After an hour, the background reverted back to normal, and I could place calls. 

The speaker and mic were dead, but the next mornign the speaker was working and today, the mic started working.  The battery doesn't seem to hold a charge like it used to, but I'm amazed it is working at all.

Kudos to Motorola!

Subversion Hosting

I'm always shocked when I encounter people and projects that aren't using version control.  After years of knowing that I can rollback to a previous version, working on files that aren't versioned scares me.  I keep everything possible checked in, including tax files, my diaries, etc.

I switched to Subversion 18 months ago and have been very happy with both my free and my paid hosting.

 

Copyright for Software Companies

Most programmers only think about copyright when management decrees that every file needs a copyright notice at the top or bottom.  Next month, I’m sure a junior developer somewhere will be assigned to update the copyright date on a source tree to show 2007, while the features that would make that product a success languish unimplemented. 

Read more about copyright for software companies on my business blog, Money and Software.

Google Search API fails sporadically with "502 Bad Gateway"

I heard about Google's web service interface to the search database back when it was released, but today was my first attempt to use it.  I'm trying to learn python, but the SOAP toolkits for python seem to be in a state of flux so I switched back to C# rather than figure out which python libraries to download. 

I experienced two problems, one minor and one major.  The doSearch method  takes a bunch of strings, and if you pass null in, you get back weird errors talking about no signature match and java.lang.String.  Passing String.Empty fixes that. 

The second problem is that, requests to search sporadically fail with "502: Bad Gateway".  From my googling (ah, the irony) for a solution, this has been a problem since January 2006, and is still unsolved. 

The solution on the forums seems to be catch the exception and try again; I didn't expect that to work, but it did.  I wrote a console app that sent the same string to the gateway 30 times, and it seemed to randomly error out.  A snippet of the output is below:

12:27:14.593 - The request failed with HTTP status 502: Bad Gateway.

12:27:19.046 - Success, found 782000 items.

12:27:32.625 - Success, found 782000 items.

12:27:35.000 - Success, found 782000 items.

12:27:35.984 - The request failed with HTTP status 502: Bad Gateway.

12:27:39.843 - Success, found 782000 items.

12:27:42.593 - Success, found 782000 items.

This makes this obviously unsuitable for anything more than playful experimentation, but since the API doesn't return ads, I guess Google doesn't care.

 

Web Service performance numbers--plenty fast for UI work

While designing Web Services, the question of "interface granularity" often comes up.  Conventional wisdom is that Web Service calls are slow, so the interface must be quite coarse to prevent performance problems.

 

Four years ago, a partner and I built a 2-tier system, a rich client app that talked directly to a database.  Towards the end of the development cycle, the users said, "We know we said this was for internal use, but we want to use this application over the Internet."

 

Bam! That is the kind of major requirements change that can kill projects.  After some research, we factored the database calls into an IDataAccess interface that had two implementors: the original database layer and a web service layer that then called the original database layer.  On startup, the app figures out which interface to use.  As our research and prototyping showed, web services are plenty fast for this scenario. 

 

My current project is also a rich client backed by web services, and we are discussing how quickly the UI needs to respond to the user.  Microsoft's performance guidelines suggest one threshold, "A good guideline for interactive response is 500 milliseconds."

 

Jim Webber estimates that SOAP adds 14 milliseconds round trip.  Network latency is usually less than 50 milliseconds round trip in the US, increasing roughly linearly to a rough maximum of 300 milliseconds worldwide. 

 

In my experience, a hit to an optimized database costs roughly 20 ms, plus latency to the database machine of another 5-10 ms.  So we are looking at 14 + 50 + 20 + 10 = 94ms.  Add in another 50 ms for authentication, authorization and business logic on the server, which leaves the UI well under the 500 ms limit.  Anyone have numbers for how much SSL increases latency?

 

I just did some performance testing that supports these numbers as reasonable.  Using a unit test that connects to the web service and uses it to insert a row in a database (everything on the same machine), I was seeing average call times of 15 ms.  That leaves room for a lot of latency before the application slows down.

 

On my project four years ago, we were forced into a finer grained (more talkative) interface by previous decisions.  These days, I'm choosing this architecture with my eyes open, and I like the view.

More Posts Next page »