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

Global Error Logging in ASP.Net MVC 6

我正在测试 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. 没有太多关于引发错误的控制器的上下文(也许我可以通过某种方式从上下文中获取它)。

我能想到的另一个选择是有一个基本控制器,它接受所有控制器都继承自的 ILoggerFactory。

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

您的问题分为两部分。 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:从你之前的 post 来看,你似乎在玩 ExceptionHandlerMiddleware(source & extension source)...使用它怎么样?...有关它的一些信息:

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

另一种执行全局错误处理的方法是使用 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
}

我正在使用 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
            }
        }