February 08, 2010

Using ELMAH with multiple applications

I’ve been using ELMAH a lot lately, and have it deployed to multiple ASP.NET applications. As I’ve said before, I love it. It’s certainly easy to set up and useful for each application to have an error log of its own.

However, sometimes I want to see a consolidated list of errors across all of those  applications. Here are a few of the things I’ve done to create what I call a “Master” ELMAH error log viewer.

First, the easy part. Each of the applications that I’ve deployed are connected to the same SQL Server database, prepared using the standard ELMAH configuration. All of them log to the same database.

The ELMAH viewer page HTTP handler, however, is designed to filter the errors by application. It does this by inferring the application name (or getting it from the config file, if you’ve specified it there), and then sending that application name to the database as a parameter for the ELMAH_GetErrorsXML and ELMAH_GetErrorXML stored procedures. The stored procedures have a WHERE clause in the SELECT statement that filters the errors. This is great for the applications individually, but we have to override that behavior if we want to have a consolidated view.

To start, I created two additional stored procedures in the database called ELMAH_GetErrorsXML_Master and ELMAH_GetErrorXML_Master. These sprocs are identical to the standard ELMAH sprocs, except I’ve removed the ‘@Application’ parameter and the ‘WHERE [Application] = @Application’ clauses.

Next (this requires downloading the source code, editing it, and recompiling), I created a ‘SQLMasterErrorLog.cs’, based on the existing ‘SQLErrorLog.cs’ class. I edited the GetErrorXml() and GetErrorsXML() methods to match the new sprocs I created, removing the @Application parameter.



For example, this:

public static SqlCommand GetErrorXml(string appName, Guid id)
{
SqlCommand command = new SqlCommand("ELMAH_GetErrorXml");
command.CommandType = CommandType.StoredProcedure;
 
SqlParameterCollection parameters = command.Parameters;
parameters.Add("@Application", SqlDbType.NVarChar, 
_maxAppNameLength).Value = appName;
parameters.Add("@ErrorId", SqlDbType.UniqueIdentifier).Value = id;
 
return command;
}

became this:

public static SqlCommand GetErrorXml(string appName, Guid id)
{
SqlCommand command = new SqlCommand("ELMAH_GetErrorXml_Master");
command.CommandType = CommandType.StoredProcedure;
 
SqlParameterCollection parameters = command.Parameters;
parameters.Add("@ErrorId", SqlDbType.UniqueIdentifier).Value = id;
 
return command;
}

GetErrorsXML() got the same treatment. I also updated the <summary> description and the ‘Name’ property to reflect the changes I had made.

Next, I changed the ErrorLogPage.cs file to display an extra column in the log viewer table for the Application Name. In the "’RenderErrors()” method, I added these lines (top line only):

headRow.Cells.Add(FormatCell(new TableHeaderCell(), "App", "app-col"));
headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Host", "host-col"));
and

bodyRow.Cells.Add(FormatCell(new TableCell(), error.ApplicationName, "app-col"));
bodyRow.Cells.Add(FormatCell(new TableCell(), error.HostName, "host-col"));
After saving and compiling, I had a new elmah.dll that contains a “SQLMaster” error log.

The final step was creating a web application to hold the new ELMAH viewer. I made an empty ASP.NET application, with nothing in it but a Default.aspx file that really only exists to autoforward to the errors.axd handler. With one small tweak to the standard configuration, I was off and running:

<errorLog type="Elmah.SqlMasterErrorLog, Elmah" connectionStringName="elmah"/>