Integrating ELMAH with MongoDB

MongoDB is by far one of the most well-known and powerful documental databases created in the open source community. The simplicity that you find in this database is also another factor that help a lot in adoption, as you don’t need to know much to start using it.  

ELMAH, which stands for Error Logging Modules and Handlers, is another famous open source project in the .NET world. Almost any ASP.NET developer in the planet is already familiar with the project and what you can do it with it. If you haven’t heard of it yet, it is an extensible framework that provides an application-wide error logging facility for ASP.NET applications.

MongoDB represents a group of documents as a collection. As an analogy with relational databases, a document could be seen as a row, and a collection as a table. The main difference is that collections are schema-free and can store any kind of document, and a document can have any structure.

Collections are usually created dynamically and automatically grow in size to fit additional data.  However, there is an specific collection type called “capped collection”, which is created in advance and is fixed in size. A capped collection behave like circular queues, so if the collection runs out of space, the oldest documents will be deleted, and the new ones will take their place. Documents in this type of collection can not be moved or deleted, making this collection extremely fast for new insertions. There is no need to allocate additional space, or search through a free list to find the right place to put a document. The inserted document can always be placed directly at the tail of the collection and overwrite old documents if needed.

This makes capped collections a great fit for use cases like logging. Having said that, capped collections in MongoDB are also a great candidate for being used in a logging provider for ELMAH. 

ELMAH organizes log entries per application, so I though it would be a good idea to use the same approach in MongoDB and have separate collections for applications. For this implementation, I used mongodb-csharp.

When a new entry needs to be persisted in the database, I first check whether the collection exists and I create one on the fly in case it does not.

using (var mongo = new Mongo(_connectionString))
{
    mongo.Connect();
 
    var master = mongo.GetDatabase("master");
 
    IMongoCollection collection = null;
 
    if (!master.GetCollectionNames().Any(collectionName => collectionName.EndsWith(ApplicationName)))
    {
        // Create event collection
        var options = new Document();
        options.Add("capped", true);
        options.Add("max", _maxEntriesCount);
        options.Add("size", _defaultCollectionSize);
        
        master.Metadata.CreateCollection(ApplicationName, options);
    
        var indexes = new Document();
        indexes.Add("id", 1);
 
        collection = master.GetCollection(ApplicationName);
        collection.MetaData.Indexes.Add("id", indexes);
    }

As you can see, that’s something really easy to accomplish with a few lines of code. I also created an index for the “id” property, which is the one that ELMAH uses for searching specific entries.

Once the collection is created, the ELMAH log entries need to be transformed to a document and stored in the collection, so I wrote an small helper for doing that.

var document = ErrorDocument.EncodeDocument(error);
document.Add("id", id);
 
collection.Save(document);

Getting the documents from the collection is also very straightforward, as the MongoDB API already support paging, which is something the ELMHA providers must provide.

mongo.Connect();
 
var master = mongo.GetDatabase("master");
 
var collection = master.GetCollection(ApplicationName);
var documents = collection.FindAll()
    .Skip(pageIndex * pageSize)
    .Limit(pageSize)
    .Documents;
 
foreach (var document in documents)
{
    var errorLog = ErrorDocument.DecodeError(document);
    errorEntryList.Add(new ErrorLogEntry(this, (string)document["id"], errorLog));
}

You can use this provider in any existing ASP.NET web application by adding this configuration to define the provider and the connection string to an existing MongoDB instance.

<elmah>
    <errorLog type="Elmah.MongoDb.MongoDbErrorLog, Elmah.MongoDb" connectionStringName="ELMAH.MongoDB" />
</elmah>
<connectionStrings>
    <add name="ELMAH.MongoDB" connectionString="Server=localhost:27017"/>
</connectionStrings>

The code is available to download from here. (Don’t forget to download the MongoDB drivers to compile the code)

No Comments