571

I am trying to use ELMAH to log errors in my ASP.NET MVC application, however when I use the [HandleError] attribute on my controllers ELMAH doesn't log any errors when they occur.

As I am guessing its because ELMAH only logs unhandled errors and the [HandleError] attribute is handling the error so thus no need to log it.

How do I modify or how would I go about modifying the attribute so ELMAH can know that there was an error and log it..

Edit: Let me make sure everyone understands, I know I can modify the attribute thats not the question I'm asking... ELMAH gets bypassed when using the handleerror attribute meaning it won't see that there was an error because it was handled already by the attribute... What I am asking is there a way to make ELMAH see the error and log it even though the attribute handled it...I searched around and don't see any methods to call to force it to log the error....

dswatik
  • 9,129
  • 10
  • 38
  • 53
  • 11
    Hmm, strange - we don't use the HandleErrorAttribute - Elmah is setup in our web.config's section. Are there benefits to using the HandleErrorAttribute? – Jarrod Dixon Apr 22 '09 at 03:39
  • 1
    Well yea I think you don't get that annoying querystring in the URL plus when an error occurs the url doesn't get redirected to the one specified in the custom error in the web.config... just cleaner to me – dswatik Apr 22 '09 at 11:51
  • @dswatik Yeah, I guess an error view that appears on the current url, instead of a redirected one, might be cleaner - we'll check it out! – Jarrod Dixon Apr 22 '09 at 22:35
  • 9
    @Jarrod - it'd be nice to see what's "custom" about your ELMAH fork. – Scott Hanselman Apr 24 '09 at 00:07
  • 3
    @dswatik You can also prevent redirects by setting redirectMode to ResponseRewrite in web.config. See http://blog.turlov.com/2009/01/search-engine-friendly-error-handling.html – Pavel Chuchuva Jul 26 '10 at 06:49
  • 6
    I kept running into web documentation and posts talking about the [HandleError] attribute and Elmah, but I wasn't see the behaviour this solves (e.g. Elmah not logging the "handled" error) when I setup the dummy case. This is because as of Elmah.MVC 2.0.x this custom HandleErrorAttribute is no longer required; it's included in the nuget package. – plyawn Oct 13 '12 at 21:14
  • [Write Log In Mvc Using HandleErrorInfo](http://www.jquery2dotnet.com/2013/03/write-log-in-mvc-using-handleerrorinfo.html) – Sender Jun 15 '14 at 08:06

8 Answers8

510

You can subclass HandleErrorAttribute and override its OnException member (no need to copy) so that it logs the exception with ELMAH and only if the base implementation handles it. The minimal amount of code you need is as follows:

using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled) 
            return;
        var httpContext = context.HttpContext.ApplicationInstance.Context;
        var signal = ErrorSignal.FromContext(httpContext);
        signal.Raise(context.Exception, httpContext);
    }
}

The base implementation is invoked first, giving it a chance to mark the exception as being handled. Only then is the exception signaled. The above code is simple and may cause issues if used in an environment where the HttpContext may not be available, such as testing. As a result, you will want code that is that is more defensive (at the cost of being slightly longer):

using System.Web;
using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled       // if unhandled, will be logged anyhow
            || TryRaiseErrorSignal(context) // prefer signaling, if possible
            || IsFiltered(context))         // filtered?
            return;

        LogException(context);
    }

    private static bool TryRaiseErrorSignal(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        if (httpContext == null)
            return false;
        var signal = ErrorSignal.FromContext(httpContext);
        if (signal == null)
            return false;
        signal.Raise(context.Exception, httpContext);
        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, 
                              GetHttpContextImpl(context.HttpContext));
        return config.Assertion.Test(testContext);
    }

    private static void LogException(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        var error = new Error(context.Exception, httpContext);
        ErrorLog.GetDefault(httpContext).Log(error);
    }

    private static HttpContext GetHttpContextImpl(HttpContextBase context)
    {
        return context.ApplicationInstance.Context;
    }
}

This second version will try to use error signaling from ELMAH first, which involves the fully configured pipeline like logging, mailing, filtering and what have you. Failing that, it attempts to see whether the error should be filtered. If not, the error is simply logged. This implementation does not handle mail notifications. If the exception can be signaled then a mail will be sent if configured to do so.

You may also have to take care that if multiple HandleErrorAttribute instances are in effect then duplicate logging does not occur, but the above two examples should get your started.

Atif Aziz
  • 36,108
  • 16
  • 64
  • 74
  • I can't get the ElmahHandleErrorAttribute to work My present configuration works fine using elmah in modules and handlers I have removed the [HandleError] from my controllers I want to try the ElmahHandleErrorAttribute I have set the ElmahHandleErrorAttributeon my ApplicationController (the base class of all my controllers) but it's never called when I debug and set a break point in OnException what am I missing ? – freddoo Nov 05 '09 at 15:10
  • 1
    Excellent. I wasn't trying to implement Elmah at all. I was just trying to hook up my own error reporting I've used for years in a way that works well with MVC. Your code gave me a starting point. +1 – Steve Wortham Nov 18 '09 at 03:29
  • Thank you. I thought I was losing my mind, when really I was obverving the "HandleError is ignored if customErrors set to RemoteOnly and you're on the localhost" behavior. Turns out the code in my Application_Error event as for naught! – Nicholas Piasecki Nov 19 '09 at 15:47
  • 1
    When you have a chain of HandleErrors, like [HandleError(Order = 1, ExceptionType = typeof(NotFound), View = "Error/NotFound"), HandleError(Order = 2, View = "Error/Unexpected")] you end up with ELMAH sending you a chain of emails for a single error. Is there any way to avoid that? – Pablo Fernandez Jan 08 '10 at 21:41
  • 1
    There seems to be a bug in the above code. The first code logs if exception is handled but the latter tries to log when the exception is not handled. I think we only want to log when it's handled by the base class. – Jiho Han May 19 '10 at 18:25
  • I would like to add a custom view to this solution but calling context.Result = new ViewResult() { ViewName = "Error" }; does not seem to do anything. – Myster Aug 20 '10 at 00:19
  • in regard to my previous comment, I was missing: context.ExceptionHandled = true; – Myster Aug 20 '10 at 02:23
  • 19
    You don't need to subclass HandleErrorAttribute. You can simply have an IExceptionFilter implementation and have it registered together with the HandleErrorAttribute. Also I don't get why do you need to have a fallback in case ErrorSignal.Raise(..) fails. If the pipeline is badly configured it should be fixed. For a 5 liner IExceptionFilter check point 4. here - http://ivanz.com/2011/05/08/asp-net-mvc-magical-error-logging-with-elmah/ – Ivan Zlatev May 09 '11 at 12:22
  • Why not remove filters.Add(new HandleErrorAttribute()); and use the custom errors section in web.config to redirect to your own error view? – Ryan Sampson Sep 01 '11 at 16:27
  • Hi, I would like to set DbConnection (as DbConnectionFactory) without defined in config file. How should I do? – Nuri YILMAZ Jan 26 '12 at 14:58
  • This filter is available as a NuGet package for either [ASP.NET MVC](http://nuget.org/packages/Elmah.Contrib.Mvc) or [ASP.NET Web API](http://nuget.org/packages/Elmah.Contrib.WebApi). – Richard Dingwall May 21 '12 at 09:02
  • ExceptionContext contains the HttpContext, there is no reason to use HttpContext.Current. Actually it should be avoided. – MartinF Jun 04 '12 at 21:04
  • @MartinF `HttpContext.Current` was used because ELMAH 1.x works with `HttpContext` and not `HttpContextBase`. However, I've improved the `HandleErrorAttribute` implementation to get the `HttpContext` object from an `HttpContextBase` object and as found in `ExceptionContext`. No more calls to `HttpContext.Current`. – Atif Aziz Jun 06 '12 at 11:58
  • 5
    Please can you comment on the answer below by @IvanZlatev with regards to applicability, shortcomings etc. People are commenting that it is easier/shorter/simpler and achieves the same as your answer and as such should be marked as the correct answer. It would be good to have your perspective on this and achieve some clarity with these answers. – Andrew Jun 22 '12 at 16:39
  • I wish to catch the error and then show it in a jQueryUI dialog (or even in a simple Javascript alert) WITHOUT any redirection. How can I do this with Elmah? Thank you. – Silvio Delgado Sep 14 '12 at 13:58
  • Is it possible to get MVC action name & parameter inside Emailing method? I posted a [question here](http://stackoverflow.com/questions/20640010/accessing-action-name-and-parameters-in-elmah-errormail-mailing) – Murali Murugesan Dec 17 '13 at 16:53
  • 8
    Is this still relevant or does ELMAH.MVC handles this? – Romias Apr 03 '14 at 13:45
  • 2
    Even I would like to know whether it's still relevant in today's version – refactor Jun 15 '15 at 10:16
304

Sorry, but I think the accepted answer is an overkill. All you need to do is this:

public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
    public void OnException (ExceptionContext context)
    {
        // Log only handled exceptions, because all other will be caught by ELMAH anyway.
        if (context.ExceptionHandled)
            ErrorSignal.FromCurrentContext().Raise(context.Exception);
    }
}

and then register it (order is important) in Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
Ivan Zlatev
  • 13,016
  • 9
  • 41
  • 50
  • 3
    +1 Very nice, no need to extend the `HandleErrorAttribute`, no need to override `OnException` on `BaseController`. This is suppose to the the accepted answer. – CallMeLaNN Jun 10 '11 at 07:35
  • @Ivan Zlatev any ideas how to add some flag or error message prefix or something in such case to be able to determine/see in elmah error log which errors was handled and which not? – angularrocks.com Jul 27 '11 at 21:46
  • 1
    @bigb I think you would have to wrap the exception in your own exception type to append things to the exception message, etc (e.g. `new UnhandledLoggedException(Exception thrown)` which appends something to the `Message` prior to returning it. – Ivan Zlatev Jul 27 '11 at 22:24
  • 24
    Atif Aziz created ELMAH, I'd go with his answer – jamiebarrow Aug 05 '11 at 09:01
  • 48
    @jamiebarrow I didn't realize that, but his answer is ~2 years old and probably the API has been simplified to support the question's use cases in a shorter more self-contained manner. – Ivan Zlatev Aug 05 '11 at 20:38
  • 6
    @Ivan Zlatev really can'tt get to work `ElmahHandledErrorLoggerFilter()` elmah just logging unhandled errors, but not handled. I registered filters in correct order as you mentioned that, any thoughts? – angularrocks.com Sep 14 '11 at 06:21
  • Thanks, this pointed me in the right path although I found a few caveats with this solution (I've posted my own answer to this question that should explain why I went a different route) – Raul Vejar Jan 24 '13 at 18:13
  • 1
    @bigb Did you ever figure out why this was the case? I know it has been a long time. – Mark Mar 26 '13 at 00:13
  • @Mark heh, didn't spend time on that. – angularrocks.com Apr 11 '13 at 02:27
  • @Ivan Zlatev Does it make any difference if i dont use Try/Catch ? – Peru Apr 25 '13 at 14:31
  • This used to work for me earlier, now it seems Elmmah is logging handled errors anyway.. now I have with this filter everything twice in elmah.. – Lukas K May 20 '14 at 11:24
  • 1
    I cannot get this to work with ASP MVC 5. I can use the following answer http://stackoverflow.com/questions/88326/does-elmah-handle-caught-exceptions-as-well/841426#841426 to log the error fine, but this answer is not calling the method. I'd rather not have to manually type it each time I catch an exception! I have put the 'ElmahHandledErrorLoggerFilter' class inside my 'filter.config' file and added the 'filters.Add(new ElmahHandledErrorLoggerFilter())' line to the top of my 'RegisterGlobalFilters' function. What am I doing wrong? – Connel Jun 02 '14 at 14:16
  • @IvanZlatev should not `ElmahHandledErrorLoggerFilter` added registered last so it executes last, (After `HandleErrorAttribute` and before elmah) ? – tchelidze Nov 28 '16 at 10:08
15

There is now an ELMAH.MVC package in NuGet that includes an improved solution by Atif and also a controller that handles the elmah interface within MVC routing (no need to use that axd anymore)
The problem with that solution (and with all the ones here) is that one way or another the elmah error handler is actually handling the error, ignoring what you might want to set up as a customError tag or through ErrorHandler or your own error handler
The best solution IMHO is to create a filter that will act at the end of all the other filters and log the events that have been handled already. The elmah module should take care of loging the other errors that are unhandled by the application. This will also allow you to use the health monitor and all the other modules that can be added to asp.net to look at error events

I wrote this looking with reflector at the ErrorHandler inside elmah.mvc

public class ElmahMVCErrorFilter : IExceptionFilter
{
   private static ErrorFilterConfiguration _config;

   public void OnException(ExceptionContext context)
   {
       if (context.ExceptionHandled) //The unhandled ones will be picked by the elmah module
       {
           var e = context.Exception;
           var context2 = context.HttpContext.ApplicationInstance.Context;
           //TODO: Add additional variables to context.HttpContext.Request.ServerVariables for both handled and unhandled exceptions
           if ((context2 == null) || (!_RaiseErrorSignal(e, context2) && !_IsFiltered(e, context2)))
           {
            _LogException(e, context2);
           }
       }
   }

   private static bool _IsFiltered(System.Exception e, System.Web.HttpContext context)
   {
       if (_config == null)
       {
           _config = (context.GetSection("elmah/errorFilter") as ErrorFilterConfiguration) ?? new ErrorFilterConfiguration();
       }
       var context2 = new ErrorFilterModule.AssertionHelperContext((System.Exception)e, context);
       return _config.Assertion.Test(context2);
   }

   private static void _LogException(System.Exception e, System.Web.HttpContext context)
   {
       ErrorLog.GetDefault((System.Web.HttpContext)context).Log(new Elmah.Error((System.Exception)e, (System.Web.HttpContext)context));
   }


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

Now, in your filter config you want to do something like this:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //These filters should go at the end of the pipeline, add all error handlers before
        filters.Add(new ElmahMVCErrorFilter());
    }

Notice that I left a comment there to remind people that if they want to add a global filter that will actually handle the exception it should go BEFORE this last filter, otherwise you run into the case where the unhandled exception will be ignored by the ElmahMVCErrorFilter because it hasn't been handled and it should be loged by the Elmah module but then the next filter marks the exception as handled and the module ignores it, resulting on the exception never making it into elmah.

Now, make sure the appsettings for elmah in your webconfig look something like this:

<add key="elmah.mvc.disableHandler" value="false" /> <!-- This handles elmah controller pages, if disabled elmah pages will not work -->
<add key="elmah.mvc.disableHandleErrorFilter" value="true" /> <!-- This uses the default filter for elmah, set to disabled to use our own -->
<add key="elmah.mvc.requiresAuthentication" value="false" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.allowedRoles" value="*" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.route" value="errortracking" /> <!-- Base route for elmah pages -->

The important one here is "elmah.mvc.disableHandleErrorFilter", if this is false it will use the handler inside elmah.mvc that will actually handle the exception by using the default HandleErrorHandler that will ignore your customError settings

This setup allows you to set your own ErrorHandler tags in classes and views, while still loging those errors through the ElmahMVCErrorFilter, adding a customError configuration to your web.config through the elmah module, even writing your own Error Handlers. The only thing you need to do is remember to not add any filters that will actually handle the error before the elmah filter we've written. And I forgot to mention: no duplicates in elmah.

Raul Vejar
  • 837
  • 8
  • 14
8

You can take the code above and go one step further by introducing a custom controller factory that injects the HandleErrorWithElmah attribute into every controller.

For more infomation check out my blog series on logging in MVC. The first article covers getting Elmah set up and running for MVC.

There is a link to downloadable code at the end of the article. Hope that helps.

http://dotnetdarren.wordpress.com/

Darren
  • 335
  • 3
  • 4
7

A completely alternative solution is to not use the MVC HandleErrorAttribute, and instead rely on ASP.Net error handling, which Elmah is designed to work with.

You need to remove the default global HandleErrorAttribute from App_Start\FilterConfig (or Global.asax), and then set up an error page in your Web.config:

<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />

Note, this can be an MVC routed URL, so the above would redirect to the ErrorController.Index action when an error occurs.

Ross McNab
  • 11,337
  • 3
  • 36
  • 34
7

I'm new in ASP.NET MVC. I faced the same problem, the following is my workable in my Erorr.vbhtml (it work if you only need to log the error using Elmah log)

@ModelType System.Web.Mvc.HandleErrorInfo

    @Code
        ViewData("Title") = "Error"
        Dim item As HandleErrorInfo = CType(Model, HandleErrorInfo)
        //To log error with Elmah
        Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(New Elmah.Error(Model.Exception, HttpContext.Current))
    End Code

<h2>
    Sorry, an error occurred while processing your request.<br />

    @item.ActionName<br />
    @item.ControllerName<br />
    @item.Exception.Message
</h2> 

It is simply!

user716264
  • 95
  • 1
  • 1
6

For me it was very important to get email logging working. After some time I discover that this need only 2 lines of code more in Atif example.

public class HandleErrorWithElmahAttribute : HandleErrorAttribute
{
    static ElmahMVCMailModule error_mail_log = new ElmahMVCMailModule();

    public override void OnException(ExceptionContext context)
    {
        error_mail_log.Init(HttpContext.Current.ApplicationInstance);
        [...]
    }
    [...]
}

I hope this will help someone :)

Komio
  • 61
  • 1
  • 1
3

This is exactly what I needed for my MVC site configuration!

I added a little modification to the OnException method to handle multiple HandleErrorAttribute instances, as suggested by Atif Aziz:

bear in mind that you may have to take care that if multiple HandleErrorAttribute instances are in effect then duplicate logging does not occur.

I simply check context.ExceptionHandled before invoking the base class, just to know if someone else handled the exception before current handler.
It works for me and I post the code in case someone else needs it and to ask if anyone knows if I overlooked anything.

Hope it is useful:

public override void OnException(ExceptionContext context)
{
    bool exceptionHandledByPreviousHandler = context.ExceptionHandled;

    base.OnException(context);

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

    LogException(e);
}
ilmatte
  • 1,732
  • 16
  • 12
  • You don't seem to have an "if" statement around invoking base.OnException().... And (exceptionHandledByPreviousHandler || !context.ExceptionHandled || ...) cancel each other out and will always be true. Am I missing something? – joelvh Jan 18 '11 at 18:17
  • First I check if any other Handler, invoked before the current, managed the exception and I store the result in the variable: exceptionHandlerdByPreviousHandler. Then I give the chance to the current handler to manage the exception itself: base.OnException(context). – ilmatte Jun 13 '11 at 08:31
  • First I check if any other Handler, invoked before the current, managed the exception and I store the result in the variable: exceptionHandlerdByPreviousHandler. Then I give the chance to the current handler to manage the exception itself: base.OnException(context). If the exception was not previously managed it can be: 1 - It's managed by the current handler, then: exceptionHandledByPreviousHandler = false and !context.ExceptionHandled = false 2 - It's not managed by the current handler and : exceptionHandledByPreviousHandler = false and !context.ExceptionHandled true. Only case 1 will log. – ilmatte Jun 13 '11 at 08:39