ASP.Net MVC中的全局错误记录6

时间:2015-03-10 15:05:21

标签: asp.net asp.net-web-api asp.net-core-mvc

我正在测试一个MVC 6 Web Api,并希望实现登录到全局错误处理程序。只是保证在没有记录的情况下没有错误退出系统。我创建了一个ExceptionFilterAttribute并在启动时全局添加它:

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
            });
    }
}

现在,在启动时,我将此过滤器添加到:

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

这一切似乎都是一种蛮力...有没有更好的方法来使用MVC 6?

我不喜欢或不确定这种方法:

  1. 不喜欢从http上下文中提取DI
  2. 没有关于导致错误的控制器的大量背景信息(也许我可以通过某种方式从上下文中获取它)。
  3. 我能想到的另一个选择是让一个基本控制器接受所有控制器继承的ILoggerFactory。

    想知道是否存在允许插入日志的某种诊断中间件......

3 个答案:

答案 0 :(得分:11)

你的问题有2个部分。 1)DI可注射过滤器2)全局错误处理。

关于#1:您可以将ServiceFilterAttribute用于此目的。 例如:

//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
{
....
}

您应该能够从传递的ExceptionContext获取控制器的详细信息。

关于#2:从您之前的帖子看起来您正在玩ExceptionHandlerMiddlewaresource&amp; extension source)...如何使用它?...有关的信息它:

  • 此中间件是通用的,适用于任何中间件 在它之后注册,所以任何概念,如控制器/动作 info特定于MVC,中间件不会知道。
  • 此中间件不处理格式化程序写入异常。你可以 编写自己的缓冲中间件,您可以在其中修改响应 body是一个缓冲流(MemoryStream)并让MVC层 写回应。在格式化程序写异常的情况下, 您可以捕获它并发送500错误响应和详细信息。

答案 1 :(得分:1)

执行全局错误处理的另一种方法是使用ILoggerProvider

以这种方式记录异常的优点是它还捕获在属性无法捕获的位置发生的错误。例如,也可以记录Razor代码中出现的异常。

以下是依赖注入的基本示例:

提供商

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
}

答案 2 :(得分:0)

我正在使用ASP.NET Core,但这个解决方案应该可行。

我创建了一个中间件来记录通过管道的所有请求。在他们中,我只是将它包装在try catch中,所以如果它抛出异常,它会记录到我的数据库中。

       public async Task Invoke(HttpContext context)
        {
            var sessionId = GetSessionId(context);
            var path = context.Request.Path;

            var startTime = DateTime.UtcNow;
            var watch = Stopwatch.StartNew();

            try
            {
                await _next.Invoke(context);
                watch.Stop();
            }
            catch (Exception exception)
            {
                watch.Stop();

                await _errorRepo.SaveException(exception, context.Connection.RemoteIpAddress.ToString(), sessionId);
            }
            finally
            {
#pragma warning disable 4014
                _requestLogRepo.LogRequest(
                    sessionId,
                    context.User.Identity.Name,
                    context.Connection.RemoteIpAddress.ToString(),
                    context.Request.Method,
                    path,
                    context.Request.ContentType,
                    context.Request.ContentLength,
                    startTime,
                    watch.ElapsedMilliseconds);
#pragma warning restore 4014
            }
        }