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:
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...
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.
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.
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.
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.
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:
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:
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()
{
}
}
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()
{
}
}
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With