Adding filters to your ELMAH installation

comments

One of the most common configurations people use when setting up ELMAH is email exception logging so that you get notified whenever “shit’s goin’ down” on your site. This leads to a follow on issue that stems from this in that you end up receiving 100’s of emails a couple of times a week as your website gets scanned by evil doers looking for vulnerabilities – but there is a simple and elegant solution.

imageI really rate ELMAH a one of the greatest contributions made to the ASP.Net community to date. Although it has way more history/lineage that its more contemporary peers such as Glimpse or the MiniProfiler, it definitely hasn’t lost any awesomeness over the years.

I have been using ELMAH for many years now and am a huge fan, although had never thought to really blog about it until reading a recent blog post from Troy Hunt on “Gootkit’s futile attack on ASafaWeb” in which Troy comes unstuck with the first problem that people using ELMAH error emails with ASP.Net MVC often find;

Below shows my Gmail account “back in the day” before i had solved the bug-bear:

image

 

I’m getting spammed by my own website…

 

You end up receiving hundreds of emails a week from script kiddies or botnets scanning your web site for vulnerabilities and the ensuing email storm (9 times out of 10 these requests are usually looking for the WordPress admin login page @ /wp-login.php – WordPress blogs owners take note: the evil guys are looking for you).

Drowning out the noise

There are two different ways to configure ELMAH exception filters at run time, and depending on what you want to achieve they each have their uses. The creator of ELMAH Aziz Atif has kindly documented all the ways you can implement these filter exceptions in ELMAH on the project wiki here.

Before we start, be aware that all ELMAH filtering requires that you install the ELMAH filtering module if you haven’t already done so, read on.

To do this you need to:

Add the configuration sections to your web.config

<configSections>
    <sectionGroup name="elmah">
        <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
        <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
    </sectionGroup>
</configSections>

Add the error filter http module to your website

IIS Classic mode version

<httpModules>
    <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
    <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
</httpModules>

IIS Integrated mode

<modules runAllManagedModulesForAllRequests="true">
    <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler"/>
    <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
</modules>

Once you’ve done this you need to look at what solutions will work better for you based on what you really want to achieve, as there are a few different options:

  • I want to filter using web.config configuration to remove certain exceptions from my ELMAH log.
  • I want to filter using web.config configuration to stop receiving emails for certain exceptions.

    OR
  • I want to filter using code to remove certain exceptions based on a complex, granular set of rules.
  • I want to filter using code to stop receiving emails for certain exceptions based on a complex, granular set of rules.

I believe that you should really be logging all exceptions that occur on your site as by filtering out or removing certain exceptions from your ELMAH log you are usually hiding problems in your site or disabling the ability to get a full picture of the health of your website.

If you have setup the emailing of exceptions this is a slightly different story as while you still want to log all your exceptions you do not want to get spammed every time someone scans your website for vulnerabilities – as experienced by Troy in his post. Instead you want to filter out these exceptions so that you only receive notification of issues experienced by everyday visitors to your site.

Additionally If you have setup ELMAH exception e-mails there are a number of reasons why you don’t want to be spammed by your websites ELMAH installation:

  1. If you have spam filters installed there is a risk that over time your exceptions may start to be blocked by your spam filter, stopping you from having proper visibility over issues on your site.
  2. If you are receiving 100’s of exception emails from your website a day there is a good risk that you will become de-sensitized to the real issues hidden within the exception jungle that has become your inbox so that when bad things actually do occur you don’t notice them – nullifying the very reason you have setup ELMAH exception emails in the first place.

Method #1 – Filtering by web.config

My favourite approach to filtering ELMAH exceptions is to use web.config additions to define the filtering rules. This allows you to add new rules without rebuilding your project and with this gives you the flexibility to make changes to your filters “on-the-go” – and as you are probably aware, over time you will come across new types of exception “SPAM”, so having the flexibility to add to these filters simply by firing up SFTP or logging into your webserver is pretty handy.

The conventions are quite flexible and can be found at the following URL:

http://code.google.com/p/elmah/wiki/ErrorFiltering#Filtering_Declaratively_via_Configuration

The Basics

A basic summary of the ELMAH web.config configuration tags are as follows

Filter Assertions

There are many different assertions that you can use to filter exceptions by condition (take a look at the full documentation for more tips), but the ones you’ll use most are shown below. The functionality includes the assertions equal, greater than, less than, regex matching, type matching, custom JScript matching, and even more awesomly - You can write your own assertions (this is crazily powerful).

<elmah>
    <security allowRemoteAccess="0" />
    <errorFilter>
        <test>
            <or>
                <!-- exception equals a filter result -->
                <equal binding="HttpStatusCode" value="404" type="Int32" />

                <!-- exception is greater than a filter result -->
                <greater  binding="HttpStatusCode" value="404" type="Int32" />

                <!-- exception is less than a filter result -->
                <lesser binding="HttpStatusCode" value="404" type="Int32" />

                <!-- exception matches a regular expression against the exception object -->
                <regex binding="BaseException.Message" pattern="The file '/[^']+' does not exist" />

                <!-- exception matches a certain type of exception -->
                <is-type binding="BaseException" type="System.IO.FileNotFoundException" />

                <!-- exception matches a custom JScript assertion that matches multiple conditions -->
                <jscript>
                    <expression>
                        <![CDATA[
              // @assembly mscorlib
              // @assembly System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
              // @import System.IO
              // @import System.Web

              HttpStatusCode == 404
              || BaseException instanceof FileNotFoundException 
              || BaseException instanceof HttpRequestValidationException
              /* Using RegExp below (see http://msdn.microsoft.com/en-us/library/h6e2eb7w.aspx) */
              || Context.Request.UserAgent.match(/crawler/i)                      
              || Context.Request.ServerVariables['REMOTE_ADDR'] == '127.0.0.1' // IPv4 only
              ]]>
                    </expression>
                </jscript>
            </or>
        </test>
    </errorFilter>
</elmah>

Filter selectors

When you have figured out all the assertions that apply to the exceptions you want to filter out, you can join them all together using filter selectors to enable the use of more than one filter at a time (which will nearly always be the case). You can tier your filters selectors together in a parent/child relationship to even more expressively state your intentions:

<elmah>
    <security allowRemoteAccess="0" />
    <errorFilter>
        <test>
            <or>
                <!-- insert multiple filter tests for alternate assertions -->
            </or>
            <and>
                <!-- insert multiple filter tests for inclusive assertions -->
            </and>
    </test>
    </errorFilter>
</elmah>

Actually applying Filters in the web.config

Now that I've shown you the conventions used by ELMAH to express exception filters lets take a look at some examples. It is important to note that up until this point we have only been talking about writing filters to remove them from ELMAHs log, not to simply not email you about them – this is something i get to below.

Regex exception matching

Filter exceptions by Type (in this case System.Web.HttpException)

<elmah>
    <security allowRemoteAccess="0" />
    <errorFilter>
        <test>
            <or>
                <regex binding="Exception" type="System.Web.HttpException" pattern="The controller for path '/[^']+' was not found or does not implement IController." />
                <regex binding="Exception" type="System.Web.HttpException" pattern="System.Web.HttpException: A public action method '[^']+' was not found on controller '[^']+'." />
            </or>
        </test>
    </errorFilter>
</elmah>

Filter exceptions by Exception Message body:

<elmah>
    <security allowRemoteAccess="0" />
    <errorFilter>
        <test>
            <or>
                <regex binding="BaseException.Message" pattern="The file '/[^']+' does not exist" />
                <regex binding="BaseException.Message" pattern="The controller for path '/[^']+' was not found or does not implement IController." />
            </or>
        </test>
    </errorFilter>
</elmah>

Filtering Exceptions from Email only

The great part about all of these filtering assertions are they can be combined with the type of filter being used by ELMAH to filter only the Exceptions being emailed to uswhich is the very point of this post.

Only specifically target errors being emailed so that we don’t receive them:

<elmah>
    <security allowRemoteAccess="0" />
    <errorFilter>
        <test>
            <or>
                <and>
                    <regex binding="BaseException.Message" pattern="The file '/[^']+' does not exist" />
                    <regex binding="FilterSourceType.Name" pattern="mail" />
                </and>
                <and>
                    <regex binding="Exception" type="System.Web.HttpException" pattern="The controller for path '/[^']+' was not found or does not implement IController." />
                    <regex binding="FilterSourceType.Name" pattern="mail" />
                </and>
            </or>
        </test>
    </errorFilter>
</elmah>

Method #2 – Filtering by code

The other approach to creating ELMAH filters is to use code, either in your own http module or inside your Global.asax.

This obviously requires more work, but also gives you a lot more of a fine grained control over your exception logging. Again, there is a solution for just plain Exception filtering to ‘not log the exception’ as well as Email Exception filtering to not send an email for a certain type of exception.

When ELMAH raises an exception, it raises the Filtering event to see if any listeners want to dismiss it. By default it looks for methods in your Global.asax that match a certain convention and executes them as listeners. There are two different method names it looks for, each for the two functionalities we talked about in the previous paragraph: Error Filtering and Email Error Filtering.

By referencing the ELMAH Binary in your Global.asax and inserting the following two methods you can then add your own logic to enable you to interact with the exception at runtime, either to extend its functionality or to dismiss the exception from ELMAH before it gets logged or emailed (depending on which method you add your logic to).

void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
    // don't log HttpRequestValidationException's
    if (e.Exception.GetBaseException() is HttpRequestValidationException)
        e.Dismiss();
}
void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs e)
{
    // Don't email me about files not being found
    if (e.Exception.GetBaseException() is FileNotFoundException)
        e.Dismiss();
}

As you can see, with these code snippets you can have some pretty powerful control over what ELMAH does with your exceptions. This should conjure up a number of scenarios you may want to accomplish – Maybe you want to disable error emails when a certain condition is met (maybe an app setting), maybe you want to not log exceptions if they are a certain type. What you do is not important, but knowing the power and flexibility you have at hand can only be a good thing.

Further ideas

Using code you can plug into the Elmah exception events when they happen allowing you to really extend the functionality to get more value.

Some cool ideas i have come across: