In this post, you will see how to remove the elmah.axd handler from your application and learn to create a separate ASP.NET application that would be a central Elmah log viewer for your applications.
Elmah is a great exception logger for web applications. Next to the exception data (stacktrace, exception message etc.), it collects detailed request and runtime information. If I also mention that it’s easy to install and highly configurable, it comes with no surprise that it became an industrial logging standard for .NET web applications. Elmah gives you a great choice of places where you can send your logs, though a database is the one I consider most useful. What I also value in Elmah is its log viewer (elmah.axd) – it has a nice exception list and a great exception details screen – I wish the ASP.NET Yellow Screen of Death had that look :). The only thing I don’t really like in this viewer is the fact that by default, it is bound to the application that has Elmah installed and is accessible only under http://<app-url>/elmah.axd. If you do not secure access to this handler, everyone can see detailed information on your code and environment – just have a look at how many developers forget about it. In this post, I will show you how to remove the elmah.axd handler from your application and how to create a separate ASP.NET application that would be a central Elmah log viewer for your applications.
The first part is simple, just remove highlighted lines from your web.config file:
="1.0"="utf-8"
<configuration>
<system.web>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd"
type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
</system.web>
<system.webServer>
<handlers>
<add name="Elmah" path="elmah.axd" verb="POST,GET,HEAD"
type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
</handlers>
</system.webServer>
</configuration>
Now, the second part. :) Let’s create an empty ASP.NET web site and install the Elmah Core Library (no config) package from the online Nuget repository. Then let’s add an App_Code folder and create a new file in it (I named it GeneralErrorLogPageFactory.cs):
using System;
using System.Reflection;
using System.Web;
using System.Web.Configuration;
using Elmah;
namespace LowLevelDesign
{
public class GeneralErrorLogPageFactory : ErrorLogPageFactory
{
public override IHttpHandler GetHandler
(HttpContext context, string requestType, string url, string pathTranslated)
{
var appname = context.Request.Params["app"];
if (String.IsNullOrEmpty(appname))
{
if (context.Request.UrlReferrer != null && !"/stylesheet".Equals
(context.Request.PathInfo, StringComparison.OrdinalIgnoreCase))
{
appname = HttpUtility.ParseQueryString
(context.Request.UrlReferrer.Query)["app"];
context.Response.Redirect(String.Format("{0}{1}app={2}",
context.Request.RawUrl,
context.Request.Url.Query.Length > 0 ?
"&" : "?", appname));
}
}
IHttpHandler handler = base.GetHandler(context, requestType, url, pathTranslated);
var err = new MySqlErrorLog(WebConfigurationManager.ConnectionStrings
["MySqlTraceConnString"].ConnectionString);
err.ApplicationName = appname ?? "Not found";
object v = typeof(ErrorLog).GetField("_contextKey",
BindingFlags.NonPublic | BindingFlags.Static)
.GetValue(null);
context.Items[v] = err;
return handler;
}
}
}
As you can see from the above snippet, we created a class that inherits from Elmah.ErrorLogPageFactory
which implements System.Web.IHttpHandlerFactory
. This class will be responsible for creating a handler for each request to the elmah.axd address. In order to filter the logs for a given application, I need to get its name from the request parameters. If there comes a request with no app parameter, I try to deduce it from the RefererUrl
and make a redirect, otherwise I fail and set the application name to Not found. In the next lines, we replace the ErrorLog
that Elmah is using with the one specific to our application. In my case, I have all logs in a MySql database so I just create an instance of the MySqlErrorLog
class with the application name retrieved from the request parameter. When Elmah tries to get the default ErrorLog
to save the exception log (or read it), it first checks the HttpContext.Items
collection for an item with a key equal to ErrorLog._contextKey
. If it does not find it, it parses the configuration file. That is why we are replacing this item with our own ErrorLog
instance. ErrorLog._contextKey
is a private static
field so the only way to get its value is through reflection. Last step is to add our handler to the web.config file (as well as its connection string):
="1.0"="utf-8"
<configuration>
<connectionStrings>
<add name="MySqlTraceConnString" connectionString="Data Source=localhost;
Initial Catalog=DiagnosticsDB;User Id=test;Password=test" />
</connectionStrings>
<system.web>
<compilation debug="false" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd"
type="LowLevelDesign.GeneralErrorLogPageFactory" />
</httpHandlers>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd"
type="LowLevelDesign.GeneralErrorLogPageFactory" preCondition="integratedMode" />
</handlers>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="MySql.Data"
publicKeyToken="c5687fc88969c44d" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.6.4.0" newVersion="6.6.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
You can now view logs from any application by opening http://<your-app-url>/elmah.axd?app=<app-name>. Finally, you may create a default page with a list of your applications. The sample log viewer can be downloaded from my .NET Diagnostics Toolkit page.