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


175

背景

我正在为客户端开发API服务层,并且已要求我全局捕获和记录所有错误。

因此,虽然可以通过使用ELMAH或将类似的内容添加到来轻松处理未知端点(或动作)之类的内容Global.asax

protected void Application_Error()
{
     Exception unhandledException = Server.GetLastError();
     //do more stuff
}

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

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]属性装饰单个控制器。


我也有同样的问题。未处理的异常可以很好地捕获在异常过滤器属性中,但是当我抛出新的异常时,它不会陷入异常过滤器属性中,对此有什么想法吗?
daveBM

1
仍然无法捕获未知的api控制器调用,例如myhost / api / undefinedapicontroller错误。Application_error和Exception过滤器代码未执行。怎么也抓住他们?
Andrus

1
全局错误处理已添加到WebAPI v2.1。看到这里我的回应:stackoverflow.com/questions/17449400/...
DarrellNorton

1
在某些情况下,这不会捕获错误,例如“找不到资源”或控制器构造函数中的错误。请参阅此处:aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/…–
Jordan Morris

嗨,@ Matt。您已将答案写成问题的一部分,但这并不是SO中的最佳实践。这里的答案应与问题分开。您能否将其写为一个单独的答案(可以使用底部的“回答自己的问题”蓝色按钮)。
sashoalm

Answers:


56

如果您的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;
        }
    }
    

我希望它是如此的简单,但是错误仍然没有被发现。我已更新问题以避免混淆。谢谢。
Matt Cashatt

@MatthewPatrickCashatt,如果未在Application_Error事件中捕获此异常,则意味着之前有其他代码正在使用它。例如,您可能有一些自定义的HandleErrorAttributes,自定义的模块,...在其他数不胜数的其他地方,可以捕获和处理异常。但是,执行此操作的最佳位置是Application_Error事件,因为这是所有未处理的异常将要结束的地方。
Darin Dimitrov

再次感谢,但是无论如何,该/test示例都不会受到欢迎。我在第一行(Exception unhandledException = . . .)上设置了一个断点,但是在/test场景中无法达到该断点。但是,如果输入伪造的URL,则会遇到断点。
Matt Cashatt

1
@MatthewPatrickCashatt,您完全正确。该Application_Error事件不是处理Web API异常的正确位置,因为并非在所有情况下都将触发该事件。我找到了一篇非常详细的文章,解释了实现该目标的各种可能性:weblogs.asp.net/fredriknormen/archive/2012/06/11/…–
Darin Dimitrov

1
@Darin Dimitrov 仍然无法捕获未知的api控制器调用,例如myhost / api / undefinedapi错误。Application_error和Exception过滤器代码未执行。怎么也抓住他们?
Andrus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.