Wednesday, March 2, 2011

Integrating ELMAH into your ASP.NET MVC project

What is ELMAH? ELMAH stands for Error Logging Modules and Handlers. So it does what it says, logging errors. It's a plug-able stand alone module that you can integrate with your project quite easily.

From ELMAH's site:
Once ELMAH has been dropped into a running web application and configured appropriately, you get the following facilities without changing a single line of your code:

  • Logging of nearly all unhandled exceptions.
  • A web page to remotely view the entire log of recoded exceptions.
  • A web page to remotely view the full details of any one logged exception.
  • In many cases, you can review the original yellow screen of death that ASP.NET generated for a given exception, even with customErrors mode turned off.
  • An e-mail notification of each error at the time it occurs.
  • An RSS feed of the last 15 errors from the log. 
Sounds awesome! Hell yes - beats writing your own any day! How do you integrate it with ASP.NET MVC project?

1. Download ELMAH
You can get the compiled assemblies or the source files here: http://code.google.com/p/elmah/

2. Reference ELMAH's assemblies
If you get the compiled assemblies, just make a reference to Elmah.dll. If you get the source, you will need to build it and then reference it (either by project or by binary).

3. Modify web.config
Open your web.config and make these changes:
a. Add this "configSections" within "configuration"
<configSections>
        <sectionGroup name="elmah">
            <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
            <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
            <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
            <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
        </sectionGroup>
    </configSections>
b. Add this "elmah" section within "configuration" under the "configSections". This will enable ELMAH to save the error log to the database using "MyAppConnectionString" connection string. If you want to save the log into XML files on a local disk, you can uncomment the second line (and comment the 4th line) and specify the saving location under "logPath".
<elmah>
        <!--<errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data"/>-->
        <security allowRemoteAccess="1"/>
        <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="MyAppConnectionString"/>
    </elmah>
c. Within "system.web" section, add these lines:
<httpHandlers>
            <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
        </httpHandlers>
        <httpModules>
            <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
        </httpModules>
d. Within "configuration", configure the permission to see the log. Basically it will deny all users unless users are logged in and user name is listed in the "allow" list. Enable that by adding these lines:
<location path="elmah.axd">
        <system.web>
            <authorization>
                <allow users="admin,joe,bob"/>
                <deny users="*"/>
            </authorization>
        </system.web>
    </location>

4. Modify global.asax
Check in your global.asax whether this line exists:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
If it is, then no more changes are needed in the global.asax.

5. Preparing Database
If you elect to store the log in the database, then some tables and stored procedures must be created before ELMAH can be functional. Get the script to create all those tables & stored procedures here: http://code.google.com/p/elmah/wiki/Downloads?tm=2 and run it.

That should be it and ELMAH is ready to run - you can start accessing ELMAH under /elmah.axd from your root url.

But I went a step further - I want all exception, including handled one to also be captured in ELMAH. ASP.NET MVC provides an filter-attribute called "HandleError" to handle/catch all errors/exceptions happening in the controller or bubble all the way to the controller. So instead of using that, I made my own HandleError attribute that will log to ELMAH - and replace the stock [HandleError] with my [ELMAHHandleError] attribute.

6. Create [ELMAHHandleError] for all my controllers.
I took this code from NerdDinner example that ASP.NET team provides in CodePlex.
public class ELMAHHandleErrorAttribute : HandleErrorAttribute {
        public override void OnException(ExceptionContext context) {
            base.OnException(context);

            var e = context.Exception;
            if (!context.ExceptionHandled   // if unhandled, will be logged anyhow
                    || RaiseErrorSignal(e)      // prefer signaling, if possible
                    || IsFiltered(context))     // filtered?
                return;

            LogException(e);
        }

        private static bool RaiseErrorSignal(Exception e) {
            var context = HttpContext.Current;
            if (context == null)
                return false;
            var signal = ErrorSignal.FromContext(context);
            if (signal == null)
                return false;
            signal.Raise(e, context);
            return true;
        }

        private static bool IsFiltered(ExceptionContext context) {
            var config = context.HttpContext.GetSection("elmah/errorFilter") as ErrorFilterConfiguration;

            if (config == null) return false;

            var testContext = new ErrorFilterModule.AssertionHelperContext(context.Exception, HttpContext.Current);

            return config.Assertion.Test(testContext);
        }

        private static void LogException(Exception e) {
            var context = HttpContext.Current;
            ErrorLog.GetDefault(context).Log(new Error(e, context));
        }
    }

That's it!

For further reading:

2 comments:

neha said...

Can I add elmah tables and procedures in my own application database when I am using SQL CE? Do let me know

Thanks
Neha

Anonymous said...

what about forwarding to custom error when an exception is thrown?