swannee swannee - 2 months ago 34
ASP.NET (C#) Question

Global Error Logging in ASP.Net MVC 6

I'm testing out an MVC 6 Web Api and wanted to implement logging into a global error handler. Just guaranteeing no errors get out of the system without being logged. I created an ExceptionFilterAttribute and added it globally in the startup:

public class AppExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
//Notice pulling from HttpContext Application Svcs -- don't like that
var loggerFactory = (ILoggerFactory)context.HttpContext.ApplicationServices.GetService(typeof (ILoggerFactory));

var logger = loggerFactory.Create("MyWeb.Web.Api");
logger.WriteError(2, "Error Occurred", context.Exception);

context.Result = new JsonResult(
new
{
context.Exception.Message,
context.Exception.StackTrace
});
}
}


Now in the startup, I'm adding this filter in:

services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new AppExceptionFilterAttribute());
});


This all seems kind of brute force...is there a better way to get here using MVC 6?

Things I don't like or am unsure about with this approach:


  1. Don't like pulling DI from http context

  2. Don't have much context about the controller that originated the error (perhaps I can get it from the context in some way).



The other option I can think of is having a base controller that accepts an ILoggerFactory that all controllers inherit from.

Was wondering if there was some kind of diagnostics middleware that would allow logging to be inserted...

Answer

You question has 2 parts. 1) DI injectable filters 2) Global error handling.

Regarding #1: You can use ServiceFilterAttribute for this purpose. Example:

//Modify your filter to be like this to get the logger factory DI injectable.
public class AppExceptionFilterAttribute : ExceptionFilterAttribute
{
    private readonly ILogger _logger;
    public AppExceptionFilterAttribute(ILoggerFactory loggerfactory)
    {
       _logger = loggerFactory.CreateLogger<AppExceptionFilterAttribute>();
    }
    public override void OnException(ExceptionContext context)
    {
        //...
    }
}

//Register your filter as a service (Note this filter need not be an attribute as such)
services.AddTransient<AppExceptionFilterAttribute>();

//On the controller/action where you want to apply this filter,
//decorate them like
[ServiceFilter(typeof(AppExceptionFilterAttribute))]
public class HomeController : Controller
{
....
}

You should be able to get the details of the controller from the ExceptionContext that is passed.

Regarding #2: From your previous post looks like you were playing with ExceptionHandlerMiddleware(source & extension source)...how about using that?...some info regarding it:

  • This middleware is generic and is applicable to any middleware which is registered after it and so any concepts like controller/action info is specific to MVC which that middleware wouldn't be aware of.
  • This middleware does not handle formatter write exceptions. You could write your own buffering middleware where you can modify the response body to be a buffered stream(MemoryStream) and let the MVC layer write the response to it. In the case of formatter write exceptions, you can catch it and send a 500 error response with details.