如何为 C# MVC4 WebAPI 应用程序全局记录所有异常?

Posted

技术标签:

【中文标题】如何为 C# MVC4 WebAPI 应用程序全局记录所有异常?【英文标题】:How do I log ALL exceptions globally for a C# MVC4 WebAPI app? 【发布时间】:2013-02-16 13:20:36 【问题描述】:

背景

我正在为客户开发一个 API 服务层,我被要求在全局范围内捕获并记录所有错误。

因此,虽然通过使用 ELMAH 或向Global.asax 添加类似的内容可以轻松处理未知端点(或操作)之类的事情:

protected void Application_Error()

     Exception unhandledException = Server.GetLastError();
     //do more stuff

。 . .unhandled 与路由无关的错误不会被记录。例如:

public class ReportController : ApiController

    public int test()
    
        var foo = Convert.ToInt32("a");//Will throw error but isn't logged!!
        return foo;
    

我还尝试通过注册此过滤器来全局设置[HandleError] 属性:

filters.Add(new HandleErrorAttribute());

但这也不会记录所有错误。

问题/疑问

我如何拦截上面调用/test 产生的错误,以便我可以记录它们?似乎这个答案应该是显而易见的,但我已经尝试了到目前为止我能想到的一切。

理想情况下,我想在错误日志中添加一些内容,例如请求用户的 IP 地址、日期、时间等。我还希望能够在遇到错误时自动向支持人员发送电子邮件。只要我能在这些错误发生时拦截它们,我就能做到这一切!

已解决!

感谢 Darin Dimitrov,我接受了他的回答,我明白了这一点。 WebAPI以与常规 MVC 控制器相同的方式处理错误。

这是有效的:

1) 向您的命名空间添加自定义过滤器:

public class ExceptionHandlingAttribute : ExceptionFilterAttribute

    public override void OnException(HttpActionExecutedContext context)
    
        if (context.Exception is BusinessException)
        
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            );

        

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        );
    

2) 现在在 WebApiConfig 类中全局注册过滤器:

public static class WebApiConfig

     public static void Register(HttpConfiguration config)
     
         config.Routes.MapHttpRoute("DefaultApi", "api/controller/action/id", new  id = RouteParameter.Optional );
         config.Filters.Add(new ExceptionHandlingAttribute());
     

或者你可以跳过注册,只用[ExceptionHandling]属性装饰一个控制器。

【问题讨论】:

我也有同样的问题。未处理的异常可以很好地被异常过滤器属性捕获,但是当我抛出一个新异常时,它不会被异常过滤器属性捕获,对此有什么想法吗? 未知的 api 控制器调用,如 myhost/api/undefinedapicontroller 错误仍然没有被捕获。不执行 Application_error 和 Exception 过滤器代码。如何也抓住他们? WebAPI v2.1 添加了全局错误处理。在这里查看我的回复:***.com/questions/17449400/… 这不会在某些情况下捕获错误,例如“找不到资源”或控制器构造函数中的错误。参考这里:aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/… 嗨,@Matt。您已将答案写为问题的一部分,但这不是 SO 中的最佳实践。这里的答案应该与问题分开。您能否将其写为单独的答案(您可以使用底部的“回答您自己的问题”蓝色按钮)。 【参考方案1】:

作为对先前答案的补充。

昨天,ASP.NET Web API 2.1 正式发布为released。 它提供了另一个在全球范围内处理异常的机会。 详细信息在sample 中给出。

简而言之,您添加了全局异常记录器和/或全局异常处理程序(只有一个)。 您将它们添加到配置中:

public static void Register(HttpConfiguration config)

  config.MapHttpAttributeRoutes();

  // There can be multiple exception loggers.
  // (By default, no exception loggers are registered.)
  config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

  // There must be exactly one exception handler.
  // (There is a default one that may be replaced.)
  config.Services.Replace(typeof(IExceptionHandler), new GenericTextExceptionHandler());

以及他们的实现:

public class ElmahExceptionLogger : ExceptionLogger

  public override void Log(ExceptionLoggerContext context)
  
    ...
  


public class GenericTextExceptionHandler : ExceptionHandler

  public override void Handle(ExceptionHandlerContext context)
  
    context.Result = new InternalServerErrorTextPlainResult(
      "An unhandled exception occurred; check the log for more information.",
      Encoding.UTF8,
      context.Request);
  

【讨论】:

这非常有效。我同时记录和处理(因为我得到了 logID 并将其传回,以便用户可以添加评论),所以我将 Result 设置为新的 ResponseMessageResult。这个问题困扰了我一段时间,谢谢。【参考方案2】:

你有没有想过做一些像处理错误操作过滤器这样的事情

[HandleError]
public class BaseController : Controller ...

您还可以创建[HandleError] 的自定义版本,您可以使用该版本将错误信息和所有其他详细信息写入日志

【讨论】:

谢谢,但我已经在全球范围内设置了。它提出了与上述相同的问题,并非所有错误都被记录。【参考方案3】:

为什么要重新抛出等?这有效,它将使服务返回状态 500 等

public class LogExceptionFilter : ExceptionFilterAttribute

    private static readonly ILog log = LogManager.GetLogger(typeof (LogExceptionFilter));

    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    
        log.Error("Unhandeled Exception", actionExecutedContext.Exception);
        base.OnException(actionExecutedContext);
    

【讨论】:

【参考方案4】:

如果您的 Web API 托管在 ASP.NET 应用程序中,则将针对代码中所有未处理的异常调用 Application_Error 事件,包括您显示的测试操作中的异常。所以你所要做的就是在 Application_Error 事件中处理这个异常。在您展示的示例代码中,您只处理HttpException 类型的异常,Convert.ToInt32("a") 代码显然不是这种情况。因此,请确保您在其中记录并处理所有异常:

protected void Application_Error()

    Exception unhandledException = Server.GetLastError();
    HttpException httpException = unhandledException as HttpException;
    if (httpException == null)
    
        Exception innerException = unhandledException.InnerException;
        httpException = innerException as HttpException;
    

    if (httpException != null)
    
        int httpCode = httpException.GetHttpCode();
        switch (httpCode)
        
            case (int)HttpStatusCode.Unauthorized:
                Response.Redirect("/Http/Error401");
                break;

            // TODO: don't forget that here you have many other status codes to test 
            // and handle in addition to 401.
        
        else
        
            // It was not an HttpException. This will be executed for your test action.
            // Here you should log and handle this case. Use the unhandledException instance here
        
    

Web API 中的异常处理可以在不同级别完成。这是detailed article 解释不同的可能性:

可以注册为全局异常过滤器的自定义异常过滤器属性

[AttributeUsage(AttributeTargets.All)]
public class ExceptionHandlingAttribute : ExceptionFilterAttribute

    public override void OnException(HttpActionExecutedContext context)
    
        if (context.Exception is BusinessException)
        
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
            
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "Exception"
            );
        

        //Log Critical errors
        Debug.WriteLine(context.Exception);

        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
        
            Content = new StringContent("An error occurred, please try again or contact the administrator."),
            ReasonPhrase = "Critical Exception"
        );
    

自定义操作调用程序

public class MyApiControllerActionInvoker : ApiControllerActionInvoker

    public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
    
        var result = base.InvokeActionAsync(actionContext, cancellationToken);

        if (result.Exception != null && result.Exception.GetBaseException() != null)
        
            var baseException = result.Exception.GetBaseException();

            if (baseException is BusinessException)
            
                return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                
                    Content = new StringContent(baseException.Message),
                    ReasonPhrase = "Error"

                );
            
            else
            
                //Log critical error
                Debug.WriteLine(baseException);

                return Task.Run<HttpResponseMessage>(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)
                
                    Content = new StringContent(baseException.Message),
                    ReasonPhrase = "Critical Error"
                );
            
        

        return result;
    

【讨论】:

我希望它是那么简单,但错误仍然没有被抓住。我已经更新了问题以避免混淆。谢谢。 @MatthewPatrickCashatt,如果这个异常没有在Application_Error 事件中被捕获,这意味着其他一些代码之前正在使用它。例如,您可能有一些自定义的 HandleErrorAttributes、自定义模块……还有无数其他地方可以捕获和处理异常。但最好的地方是 Application_Error 事件,因为这是所有未处理的异常都将结束的地方。 再次感谢,但无论如何,/test 示例不会受到打击。我在第一行 (Exception unhandledException = . . .) 设置了一个断点,但在 /test 场景中无法命中该断点。但是,如果我输入一个虚假的 url,就会触发断点。 @MatthewPatrickCashatt,你是完全正确的。 Application_Error 事件不是处理 Web API 异常的正确位置,因为它不会在所有情况下都被触发。我找到了一篇非常详细的文章,解释了实现这一目标的各种可能性:weblogs.asp.net/fredriknormen/archive/2012/06/11/… @Darin Dimitrov Unknown api controller calls like myhost/api/undefinedapi 错误仍未被捕获。不执行 Application_error 和 Exception 过滤器代码。如何也抓住他们?【参考方案5】:

将整个事情包装在 try/catch 中并记录未处理的异常,然后将其传递。除非有更好的内置方法。

这是一个参考Catch All (handled or unhandled) Exceptions

(编辑:哦 API)

【讨论】:

以防万一,他还需要重新抛出异常。 @DigCamara 对不起,这就是我传递它的意思。扔;应该处理。我最初说“决定是退出还是重新加载”,然后意识到他说过这是一个 API。在这种情况下,最好让应用通过传递它来决定它想要做什么。 这是一个糟糕的答案,因为它会导致在每个操作中加载重复的代码。

以上是关于如何为 C# MVC4 WebAPI 应用程序全局记录所有异常?的主要内容,如果未能解决你的问题,请参考以下文章

MVC4 DTO 和多对多关系与 WebAPI 的扩展方法

c# autofac结合WebApi的使用

如何为 dotnet core(web Api) 代码更改以及 TypeScript 代码更改启用实时重新加载

如何为 asp.net webapi 构建可重用的 .Net 客户端,包括 IQueryable 功能等

MVC4 webapi中的反序列化/模型绑定不适用于数组

如何为我的 C# 应用程序创建产品密钥?