Click here to Skip to main content
Click here to Skip to main content

Making elmah.axd, a log viewer for multiple applications

, 24 Mar 2013
Rate this:
Please Sign up or sign in to vote.
Making elmah.axd, a log viewer for multiple 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 Smile | :) . The only thing I don’t really like in this viewer is a 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 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.

First part is simple, just remove highlighted lines from your web.config file:

<?xml version="1.0" encoding="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 Smile | :)  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))
            {
                // appname was not found - let's check the referrer
                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";

            // we need to fool elmah completely
            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 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):

<?xml version="1.0" encoding="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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sebastian Solnica
Software Developer (Senior)
Poland Poland
Interested in tracing, debugging and performance tuning of the .NET applications (especially ASP.NET).
 
If you find this article interesting, maybe you would like to pay me a visit: http://lowleveldesign.wordpress.com? Smile | :)

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140814.1 | Last Updated 24 Mar 2013
Article Copyright 2013 by Sebastian Solnica
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid