C# – How to log ALL exceptions globally for a C# MVC4 WebAPI app

asp.net-web-apicerror handling

Background

I am developing an API Service Layer for a client and I have been requested to catch and log all errors globally.

So, while something like an unknown endpoint (or action) is easily handled by using ELMAH or by adding something like this to the Global.asax:

protected void Application_Error()
{
     Exception unhandledException = Server.GetLastError();
     //do more stuff
}

. . .unhandled errors that are not related to routing do not get logged. For example:

public class ReportController : ApiController
{
    public int test()
    {
        var foo = Convert.ToInt32("a");//Will throw error but isn't logged!!
        return foo;
    }
}

I have also tried setting the [HandleError] attribute globally by registering this filter:

filters.Add(new HandleErrorAttribute());

But that also does not log all errors.

Problem/Question

How do I intercept errors like the one generated by calling /test above so that I can log them? It seems that this answer should be obvious, but I have tried everything I can think of so far.

Ideally, I want to add some things to the error logging, such as the IP address of the requesting user, date, time, and so forth. I also want to be able to e-mail the support staff automatically when an error is encountered. All of this I can do if only I can intercept these errors when they happen!

RESOLVED!

Thanks to Darin Dimitrov, whose answer I accepted, I got this figured out. WebAPI does not handle errors in the same way as a regular MVC controller.

Here is what worked:

1) Add a custom filter to your namespace:

public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is BusinessException)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            });

        }

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        {
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        });
    }
}

2) Now register the filter globally in the WebApiConfig class:

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}", new { id = RouteParameter.Optional });
         config.Filters.Add(new ExceptionHandlingAttribute());
     }
}

OR you can skip registration and just decorate a single controller with the [ExceptionHandling] attribute.

Best Answer

As an addition to previous answers.

Yesterday, ASP.NET Web API 2.1 was oficially released.
It offers another opportunity to handle exceptions globally.
The details are given in the sample.

Briefly, you add global exception loggers and/or global exception handler (only one).
You add them to configuration:

public static void Register(HttpConfiguration config)
{
  config.MapHttpAttributeRoutes();

  // There can be multiple exception loggers.
  // (By default, no exception loggers are registered.)
  config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

  // There must be exactly one exception handler.
  // (There is a default one that may be replaced.)
  config.Services.Replace(typeof(IExceptionHandler), new GenericTextExceptionHandler());
}

And their realization:

public class ElmahExceptionLogger : ExceptionLogger
{
  public override void Log(ExceptionLoggerContext context)
  {
    ...
  }
}

public class GenericTextExceptionHandler : ExceptionHandler
{
  public override void Handle(ExceptionHandlerContext context)
  {
    context.Result = new InternalServerErrorTextPlainResult(
      "An unhandled exception occurred; check the log for more information.",
      Encoding.UTF8,
      context.Request);
  }
}