Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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...

like image 890
swannee Avatar asked Mar 10 '15 15:03

swannee


People also ask

How exception is handled globally in C#?

An ExceptionFilterAttribute is used to collect unhandled exceptions. You can register it as a global filter, and it will function as a global exception handler. Another option is to use a custom middleware designed to do nothing but catch unhandled exceptions.

What is global error handling?

Global Error HandlerThis class implements the ErrorHandler class and contains a handleError method. This method is called whenever an error is thrown somewhere in the application. The error is passed as a parameter and can be processed further inside the method.

How do I register an exception filter globally?

To apply the filter globally to all Web API controllers, add an instance of the filter to the GlobalConfiguration. Configuration. Filters collection. Exception filters in this collection apply to any Web API controller action.

How can show error message in ASP NET MVC?

You can use the standard ASP.NET MVC ValidationSummary method to render a placeholder for the list of validation error messages. The ValidationSummary() method returns an unordered list (ul element) of validation messages that are in the ModelStateDictionary object.


2 Answers

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.
like image 133
Kiran Avatar answered Sep 30 '22 14:09

Kiran


An alternative way to perform global error handling is by using a ILoggerProvider.

The advantage to logging exceptions in this way is that it also captures errors which occur in places that an attribute would not catch. For example, exceptions that occur in Razor code could also be logged.

Here's a basic example with dependency injection:

Provider

public sealed class UnhandledExceptionLoggerProvider : ILoggerProvider
{
    private readonly IMyErrorRepository errorRepo;

    public UnhandledExceptionLoggerProvider(IMyErrorRepository errorRepo)
    {
        // inject whatever you need
        this.errorRepo = errorRepo;
    }

    public ILogger CreateLogger(string categoryName) =>
        new UnhandledExceptionLogger(errorRepo);

    public void Dispose()
    {
    }
}

Logger

public class UnhandledExceptionLogger : ILogger
{
    private readonly IMyErrorRepository errorRepo;

    public UnhandledExceptionLogger(IMyErrorRepository errorRepo)
    {
        this.errorRepo = errorRepo;
    }

    public IDisposable BeginScope<TState>(TState state) => 
        new NoOpDisposable();

    public bool IsEnabled(LogLevel logLevel) =>
        logLevel == LogLevel.Critical || logLevel == LogLevel.Error;

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception exception,
        Func<TState, Exception, string> formatter)
    {
        if (IsEnabled(logLevel))
        {
            errorRepo.LogError(exception);
        }
    }

    private sealed class NoOpDisposable : IDisposable
    {
        public void Dispose()
        {
        }
    }
}

Startup

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.AddTransient<IMyErrorRepository, MyErrorRepository>();
    services.AddTransient<UnhandledExceptionLoggerProvider>();
}

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory,
    UnhandledExceptionLoggerProvider provider)
{
    loggerFactory.AddProvider(provider);

    // ... all the rest of your startup code
}
like image 36
Will Ray Avatar answered Sep 30 '22 13:09

Will Ray