Logging in MVC Part 1- Elmah

This is part 1 of the MVC Logging series. Other articles in the series are:

Introduction

Logging is one of the most useful services that every production website should have.

When errors occur on your website you should be notified about them. Whilst you may think you have written perfect code and unit tested everything to the best of your ability errors can and will still happen. The database server may go down, a 3rd party website may be offline, the shared hosting environment of your website may suffer an outage, a previously undetected bug may occur and the list goes on.

Having a great logging system in place allows you to stay on top of errors when they do happen.

In this series we will be building a logging reporting system that will allow you to drill into the various events that get logged on your website.

Before we get started let’s take a look at the finished solution:

and we would also like to see some graphs so we can quickly pinpoint when errors occurred on our website:

and we also would like to have an RSS feed of our errors:

Goals

So let’s write down the goals for what we want to achieve:

1. Log all unhandled exceptions to a database

2. Log entries to our database whenever the website starts up, shuts down, recompiles etc.

3. Log any custom messages that we want to keep track of

4. Handle errors gracefully so that the end-user never sees a yellow screen of death error message.

5. Receive an email when an unhandled exception occurs

6. Allow multiple log providers to be easily swapped in and out of our website.

7. On the website, provide a logging reporting tool that lets us filter and view any message that has been logged.

8. The reporting tool should also allow us to view log entries as an RSS feed so that we can combine multiple feeds from all of the websites under our control.

9. The reporting tool should also have a dashboard or chart so that we can quickly get an overview of any activity on the website.

10. Provide an ability to manage logs, purge old logs, export logs to files or email them to someone.

Well that’s quite a list, so let’s get started!

Getting started

The first thing that we want to do is take care of unhandled exceptions so one of the best tools for that job is ELMAH. ELMAH was created by Atif Aziz and is used on many websites including StackOverflow.

To get started with ELMAH, the following article provides a very good overview of how to set it all up for a normal ASP.Net Web forms website:

http://www.asp.net/hosting/tutorials/logging-error-details-with-elmah-cs

I won’t go into all of the details here about setting up Elmah because the above article covers pretty much everything you need to know.

In simple terms you need to :

1. Download Elmah from it’s project page on CodePlex.

2. Extract the files from the zip.

3. The database script is located in the /src/Elmah directory. For SQL Server it is called “SQLServer.sql”. Run the database script to add the Elmah table and stored procs to your database.

4. Add a reference to Elmah for your website.

5. Modify the web.config file sections as explained in the article above. You can download the code I used at the end of this article to see the necessary web.config file alterations.

Configuring Elmah on MVC

However, as we will be using ELMAH on an MVC website we need to do a little bit more than the normal configuration to get it all working together nicely.

In MVC, you normally put [HandleError] attributes on your controllers to handle errors. Like this:

namespace MySampleApp.Controllers
{
 [HandleError]
 public class AccountController : Controller
 {
 }
}

But because we would like to use Elmah to handle our exceptions instead of MVC, we will replace the standard MVC [HandleError] attribute with our own custom attribute called [HandleErrorWithElmah].

The StackOverflow question below provides the necessary details:

http://stackoverflow.com/questions/766610/

The code necessary is shown below:

public class HandleErrorWithELMAHAttribute : 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));
        }
    }

At this point we could subsitute the [HandleError] attribute with our [HandleErrorWithELMAH] attribute but we would need to remember to do this on all of our MVC controllers… There has to be a better, easier way! And there is!

The solution is to create a custom Controller factory! And we will also need to write our own ActionInvoker. Let’s write that one first:

public class ErrorHandlingActionInvoker : ControllerActionInvoker
 {
 private readonly IExceptionFilter filter;

 public ErrorHandlingActionInvoker(IExceptionFilter filter)
 {
 if (filter == null)
 {
 throw new ArgumentNullException("filter");
 }

 this.filter = filter;
 }

 protected override FilterInfo GetFilters(
 ControllerContext controllerContext,
 ActionDescriptor actionDescriptor)
 {
 var filterInfo =
 base.GetFilters(controllerContext,
 actionDescriptor);

 filterInfo.ExceptionFilters.Add(this.filter);

 return filterInfo;
 }
 }

And here is the code for the custom controller factory:

public class ErrorHandlingControllerFactory : DefaultControllerFactory
 {
 public override IController CreateController(
 RequestContext requestContext,
 string controllerName)
 {
 var controller =
 base.CreateController(requestContext,
 controllerName);

 var c = controller as Controller;

 if (c != null)
 {
 c.ActionInvoker =
 new ErrorHandlingActionInvoker(
 new HandleErrorWithELMAHAttribute());
 }

 return controller;
 }
 }

The last step is to hook it all up so that happens automatically for each controller we have in our MVC project. To do that we wire up our new controller factory in the Global.asax.cs file:

protected void Application_Start()
 {
 AreaRegistration.RegisterAllAreas();

 RegisterRoutes(RouteTable.Routes);

 ControllerBuilder.Current.SetControllerFactory(new ErrorHandlingControllerFactory());
 }

So now that it is all wired up, we can actually go ahead and remove the [HandleError] attribute from all of our MVC controllers as our custom controller factory will now automatically inject the [HandleErrorWithElmah] attribute automatically.

The last step is to force an unhandled exception occurring in our application and view the elmah.axd page on our website to see the errors logged.

In the Home Controller I have this:

public ActionResult About()
 {
 // Throw a test error so that we can see that it is handled by Elmah
 // To test go to the ~/elmah.axd page to see if the error is being logged correctly
 throw new Exception("A test exception for ELMAH");

 return View();
 }

and then we just browse to the About page which will generate an exception:

and then browse to the Elmah page to verify that the error was logged correctly.

Conclusion

That ends part 1 of this series. Next time we will look at logging things like when the application starts up, shuts down and recompiles etc.

Download

The sourcecode for part 1 is on the Downloads tab of the associated Codeplex website

Tagged with: , , , ,
Posted in ASP.NET MVC
18 comments on “Logging in MVC Part 1- Elmah
  1. Darren, you have done a kickass job on this series! Nice job! A ton of answers to many questions around exception handling and logging! Bravo!

  2. David Montgomery says:

    Great series of posts. Thanks!

  3. Tim says:

    Nice post!

  4. Doug says:

    Excellent post!

  5. Yucel says:

    Hi, i am using elmah in my mvc application with helps of your article. But when i get an request like domain.com/xxx, i dont have a xxx controller i am getting exception on
    var controller =
    base.CreateController(requestContext,
    controllerName);
    The controller for path ‘/xxx’ was not found or does not implement IController.
    how do i solve this problem

  6. […] = 90; I’m trying to implement Elmah into my MVC application using this great tutorial.https://dotnetdarren.wordpress.com/2010/07/27/logging-on-mvc-part-1/Everything seems fine, but when I build, I getno suitable method found to overrideBelow is the class […]

  7. James_2JS says:

    Hi Darren,

    Nice work! Would you mind if I used some of your code in an Elmah.Mvc3.dll???
    I’m putting this together along with a ELMAH.Mvc3 NuGet package to hopefully simplify things for folks wanting to use ELMAH with Mvc3.
    It will be previewed in the elmah-sandbox/googlecode.com project
    And may even make it into a future ELMAH release!

    If you agree, let me know what accreditation you would want in the source file.

    I would hope that it could be released with the same Open Source Apache license that the rest of the ELMAH project uses.

    Thanks,

    James

    • darrenw74 says:

      Hi James,

      Thanks for getting in touch.

      Feel free to use the code to create a NuGet package for Elmah. I’d been thinking of doing this myself but I’m just too busy.

      Let me know when it’s added to the sandbox and I’ll try it out too. Good luck. And keeping the Open Source Apache License is fine.

      As to accreditation, just a name reference (Darren Weir) and a link to this blog would be appreciated.

      – Darren

      • James Driscoll says:

        Thanks Darren!
        I’ll acknowledge where appropriate (along with Atif’s work) and send you details once done.
        Preliminary trial should be there in the next couple of days.

        Cheers,

        James

  8. James Driscoll says:

    Hi Darren,

    I’ve been looking at this code in a little more detail now and am wondering if the following might be an improvement.
    The key change here is pull out the existing controller factory (if it exists) and use that if possible to create the controller.
    I think this will give a slightly better plug and play experience – particularly in the world of NuGet where we don’t know everything that the user is doing in their own App_Start code.

    What do you think??

    Cheers,

    James

    public class ErrorHandlingControllerFactory : DefaultControllerFactory
    {
    private IControllerFactory _originalControllerFactory;

    public ErrorHandlingControllerFactory()
    {
    var originalControllerFactory = ControllerBuilder.Current.GetControllerFactory();
    if (originalControllerFactory.GetType() != this.GetType())
    _originalControllerFactory = originalControllerFactory;
    }

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
    IController controller = null;

    if (_originalControllerFactory == null)
    controller = base.CreateController(requestContext, controllerName);
    else
    controller = _originalControllerFactory.CreateController(requestContext, controllerName);

    var c = controller as Controller;
    if (c != null)
    c.ActionInvoker = new ErrorHandlingActionInvoker(new HandleErrorWithElmahAttribute());

    return controller;
    }
    }

    • Lee Smith says:

      Could you not just register this as a global filter (MVC3)?

      //In Global.asax – Application_Start()
      // Register global filter
      GlobalFilters.Filters.Add(new HandleErrorWithELMAHAttribute());

  9. James Driscoll says:

    Hi Darren,

    I’ve actually implemented this now using GlobalFilters, which negates the need for your classes above.
    Many thanks anyway!

    Cheers,

    James

  10. Liam says:

    Hi Darren,

    You would you go about using this code with a Ninject controller factory?

  11. tjsoftware says:

    Nice and neat, Thanks a lot. I am using ASP.NET MVC 3. should i Change this part of configuration :
    type=”System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=2.0.0.0 …..

Leave a reply to Tejas Cancel reply