Writing custom error loggers for ELMAH

Something struck me the other day while reading a question on StackOverflow. No really good documentation on creating custom error log implementations is available anywhere. One could argue that the process is easy and while the interface is definitely easy to implement, there are still a couple of nice things to know. This post summarises our experiences while implementing the ELMAH logger for elmah.io.

Creating the project

You probably want your ELMAH logger in a separate project. Create a new Class Library through Visual Studio. In order to use the classes provided by ELMAH, you will need to install the NuGet package:

Install-Package elmah.corelibrary

Notice that I'm installing elmah.corelibrary and not ELMAH. The ELMAH package contains additional web.config transformations, which we don't need in our custom logger.

The ErrorLog class

This first thing you need to do when implementing a log target for ELMAH, is to extend the ErrorLog class. ErrorLog is ELMAH's main entry point for both logging new errors as well as providing data for elmah.axd (ELMAH's native UI).

Let's create an empty error logger:

public class MyErrorLog : Elmah.ErrorLog
{
    public override string Log(Error error)
    {
        throw new System.NotImplementedException();
    }

    public override ErrorLogEntry GetError(string id)
    {
        throw new System.NotImplementedException();
    }

    public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList)
    {
        throw new System.NotImplementedException();
    }
}

Implementing the methods Log, GetError and GetErrors is everything needed in order to start using the custom logger.

string Log(Error error)

The Log method is called by ELMAH when an unhandled error occur on your website. The method takes a parameter of type Error which contain all of the contextual information around the thrown error like the exception, the server variables and the source of the error.

Depending on where you need to store your errors, you will need to implement storing logic inside this method. Notice how the method returns a string? That's for the id of the data you generate and can be any string. For example, if you store the error in a database, the id will be the primary key of the new entity.

ErrorLogEntry GetError(string id)

The GetError method is for fetching detailed information about a single error. This method is called when clicking on an error in elmah.axd to show all of the details about the error. id corresponds to the id that you returned from the Log method.

To return the details, again this is where to write your custom logic. If you fetch the error from a database, creating a query or similar depending on the technology goes into this method. You will need to return the information as an instance of ErrorLogEntry. An ErrorLogEntry is a contextual class, holding information about the thrown error as well information about the error log containing this error.

You would normally create the ErrorLogEntry like this:

var errorLogEntry = new ErrorLogEntry(this, id, error);

The this parameter is the reference to the current error logger itself. The id parameter is the id send by ELMAH to the GetError method. The last parameter contains the actual error information embedded in an Error class. Sounds familiar? That's because we already saw that class in the Log method. Where you would need to map Error to your persistence layer in the Log method, here you will need to reverse that process by retrieving the error from your storage layer and map it into an Error.

int GetErrors(int pageIndex, int pageSize, IList errorEntryList)

The final method GetErrors will return a range of errors. This method is used to generate the list of errors shown on the frontpage of elmah.axd. The method takes a pageIndex and pageSize parameter to allow for pagination of the data. Since elmah.axd provides this feature, you will need to implement paging against your persistence layer. The pageIndex represents the page number currently visible (0, 1, 2 etc.). The pageSize represents the amount of errors that ELMAH want to fetch. The UI contains a range of options from 10 to 100. In other words, your code should be optimized enough to be able to query, fetch and map up till 100 errors from your persistence layer.

If you look at the method signature once more, you wont actually return the mapped errors as the return type of the method. The int returned from the method should be the total number of errors in the database. The actual error data goes into the errorEntryList. The content of errorEntryList should be zero or more object of type ErrorLogEntry. That's right, the same class as returned by GetError.

That's it. When all three methods are implemented, you now have a custom error logger for ELMAH. Congrats!

Configuration

Changes are that you need some configuration variables like a connection string inside your custom logger. While you can fetch app settings or similar inside the error logger, following the principles of other ELMAH loggers is considered good style. Take a look at error logger for XML files:

<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data" />

The logPath attribute tells the XmlFileErrorLog where to store the XML files generated by that error logger. So how to implement this? Say hello to IDictionary. That's right, a plain old dictionary from the times before LINQ. To allow for attributes like this, add a constructor to your error logger:

public MyErrorLog(IDictionary config)
{
    if (config == null)
    {
        throw new ArgumentNullException("config");
    }
}

ELMAH automatically maps all attributes besides the type attribute specified on the errorLog element to this dictionary. Looking up an attribute is as simple as this: config["myAttribute"].

Good to know

While we've been around most issues around implementing a custom error logger already, there are still a couple of things nice to know.

First of all, ELMAH creates a new instance of your error logger for each uncaught error. This means that you will need to think about the lifestyle of each connection you create for your error logger. If you need something to be singleton, you don't want to create it in the constructor of the error log. Alternatively you specify a singleton property on your log.

ELMAH allow for an application name to be specified as an attribute on the errorLog element named applicationName. The value of this attribute is not mapped automatically by ELMAH and must be handled by your code. In most cases, you will only need to add the following code to the constructor of your error logger:

ApplicationName = config.Contains("applicationName") ? config["applicationName"].ToString() : string.Empty;

Conclusion

Implementing a custom error logger for ELMAH is actually pretty easy. If you are thinking that the API smells a bit like the millennium, remember that ELMAH is more than 10 years old. Still, it is the most commonly used error management solution for .NET web applications with good reason.

Remember to publish your custom error loggers to NuGet and let the community know about it through the various options on Google Groups.