Click here to Skip to main content
15,891,646 members
Articles / Web Development / ASP.NET

Making elmah.axd, a Log Viewer for Multiple Applications

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
24 Mar 2013CPOL3 min read 16.5K   7   2
How to make elmah.axd
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:

XML
<?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. :) 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):

C#
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 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):

XML
<?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)


Written By
Software Developer (Senior)
Poland Poland
Interested in tracing, debugging and performance tuning of the .NET applications.

My twitter: @lowleveldesign
My website: http://www.lowleveldesign.org

Comments and Discussions

 
QuestionDoes this work correctly? Pin
Member 141083044-Jan-19 7:00
Member 141083044-Jan-19 7:00 
QuestionAdditional UI Filtering Pin
Member 1358348218-Dec-17 14:07
Member 1358348218-Dec-17 14:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.