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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15167927/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 14:20:20  来源:igfitidea点击:

How do I log ALL exceptions globally for a C# MVC4 WebAPI app?

c#asp.net-web-apierror-handling

提问by Matt Cashatt

Background

背景

I am developing an API Service Layer for a client and I have been requested to catch and log all errors globally.

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

So, while something like an unknown endpoint (or action) is easily handled by using ELMAH or by adding something like this to the Global.asax:

因此,虽然可以通过使用 ELMAH 或将类似的内容添加到Global.asax.

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

. . .unhandled errors that are not related to routing do not get logged. For example:

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

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

I have also tried setting the [HandleError]attribute globally by registering this filter:

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

filters.Add(new HandleErrorAttribute());

But that also does not log all errors.

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

Problem/Question

问题/疑问

How do I intercept errors like the one generated by calling /testabove so that I can log them? It seems that this answer should be obvious, but I have tried everything I can think of so far.

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

Ideally, I want to add some things to the error logging, such as the IP address of the requesting user, date, time, and so forth. I also want to be able to e-mail the support staff automatically when an error is encountered. All of this I can do if only I can intercept these errors when they happen!

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

RESOLVED!

解决!

Thanks to Darin Dimitrov, whose answer I accepted, I got this figured out. WebAPI does nothandle errors in the same way as a regular MVC controller.

感谢 Darin Dimitrov,我接受了他的回答,我明白了这一点。 的WebAPI并没有以同样的方式作为一个普通MVC控制器处理错误。

Here is what worked:

这是有效的:

1) Add a custom filter to your namespace:

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) Now register the filter globally in the WebApiConfigclass:

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

ORyou can skip registration and just decorate a single controller with the [ExceptionHandling]attribute.

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

采纳答案by Darin Dimitrov

If your web API is hosted inside an ASP.NET application, the Application_Errorevent will be called for all unhandled exceptions in your code, including the one in the test action you have shown. So all you have to do is handle this exception inside the Application_Error event. In the sample code you have shown you are only handling exception of type HttpExceptionwhich is obviously not the case with the Convert.ToInt32("a")code. So make sure that you log and handle all exceptions in there:

如果您的 Web API 托管在 ASP.NET 应用程序中,Application_Error则将针对代码中所有未处理的异常调用该事件,包括您显示的测试操作中的异常。所以你所要做的就是在 Application_Error 事件中处理这个异常。在您展示的示例代码中,您只处理类型的异常,HttpExceptionConvert.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
        }
    }
}
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
        }
    }
}

Exception handling in the Web API could be done at various levels. Here's a detailed articleexplaining the different possibilities:

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

  • custom exception filter attribute which could be registered as a global exception filter

    [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"
            });
        }
    }
    
  • custom action invoker

    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;
        }
    }
    
  • 可以注册为全局异常过滤器的自定义异常过滤器属性

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

回答by Tim

Wrap the whole thing in a try/catch and log the unhandled exception, then pass it on. Unless there's a better built-in way to do it.

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

Here's a reference Catch All (handled or unhandled) Exceptions

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

(edit: oh API)

(编辑:哦 API)

回答by COLD TOLD

have you thought about doing something like an handle error action filter like

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

[HandleError]
public class BaseController : Controller {...}

you can also create a custom version of [HandleError]with which you can write error info and all other details to log

您还可以创建一个自定义版本[HandleError],您可以使用该版本 编写错误信息和所有其他详细信息以进行记录

回答by Anders

Why rethrow etc? This works and it will make the service return status 500 etc

为什么要重新抛出等?这有效,它将使服务返回状态 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);
    }
}

回答by Vladimir

As an addition to previous answers.

作为之前答案的补充。

Yesterday, ASP.NET Web API 2.1 was oficially released.
It offers another opportunity to handle exceptions globally.
The details are given in the sample.

昨天,ASP.NET Web API 2.1 正式发布
它提供了另一个在全局处理异常的机会。
详细信息在示例中给出。

Briefly, you add global exception loggers and/or global exception handler (only one).
You add them to configuration:

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

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

And their realization:

以及他们的实现:

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