2013-03-01 32 views
158

背景

我正在爲客戶端開發API服務層,並且我被要求捕獲並記錄全局錯誤。如何爲C#MVC4 WebAPI應用程序全局記錄所有異常?

因此,儘管像一個未知的終結點(或行爲)很容易通過使用ELMAH或通過添加像這樣的Global.asax處理:

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

。 。與路由無關的未處理的錯誤不會被記錄。例如:

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]屬性。

+0

我有同樣的問題。未處理的異常會被捕獲到異常過濾器屬性中,但是當我拋出一個新的異常時,它不會被異常過濾器屬性捕獲,對此有什麼想法? – daveBM 2013-11-05 11:54:02

+1

未知的API控制器調用像http:// myhost/api/undefinedapicontroller錯誤仍未捕獲。 Application_error和Exception過濾器代碼不會被執行。如何抓住他們呢? – Andrus 2013-11-26 09:03:37

+1

全局錯誤處理已添加到WebAPI v2.1。請參閱我的回覆:http:// stackoverflow。com/questions/17449400/how-do-i-set-up -a-global-error-handler-in-webapi/21264726#21264726 – DarrellNorton 2014-01-21 17:13:35

回答

53

如果您的Web API託管在ASP.NET應用程序中,將調用代碼中所有未處理的異常(包括您在測試操作中顯示的異常)中的 Application_Error事件。所以你所要做的就是在Application_Error事件中處理這個異常。在示例代碼中,您顯示的只是處理 HttpException類型的異常,而 Convert.ToInt32("a")代碼顯然不是這種情況。因此,請確保您登錄和處理所有異常在那裏:在Web API中

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

異常處理可在各級進行。這裏有一個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; 
        } 
    } 
    
+1

簡單又幹淨 – tchrikch 2013-03-01 22:48:49

+0

我希望它那麼簡單,但錯誤仍然沒有被抓住。我已經更新了這個問題以避免混淆。謝謝。 – 2013-03-01 22:49:30

+0

@MatthewPatrickCashatt,如果這個異常沒有被'Application_Error'事件捕獲,這意味着其他代碼在之前正在使用它。例如,你可能有一些自定義的HandleErrorAttributes,自定義模塊......有其他地方的gazillions可以捕獲和處理異常。但是最好的地方是Application_Error事件,因爲那是所有未處理的異常都要結束的地方。 – 2013-03-01 22:51:22

2

將整個事情包裝在try/catch中並記錄未處理的異常,然後傳遞它。除非有更好的內置方式來做到這一點。

這裏有一個參考Catch All (handled or unhandled) Exceptions

(編輯:哦API)

+0

爲了以防萬一,他還需要重新拋出異常。 – DigCamara 2013-03-01 22:48:23

+0

@DigCamara對不起,這就是我的意思傳遞它。扔;應該處理的。我原本說「決定是退出還是重新加載」,然後意識到他說過這是一個API。在這種情況下,最好讓App通過傳遞來決定它想做什麼。 – Tim 2013-03-01 22:57:25

+0

這是一個不好的答案,因爲它會導致在每個操作中加載重複的代碼。 – Jansky 2017-11-27 14:17:43

2

你有沒有想過做這樣的處理錯誤行爲過濾像

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

,你還可以創建一個自定義版本[HandleError]與您可以寫錯誤信息和所有其他詳細信息登錄

+0

謝謝,但我已經在全球設置了。它提出了和上面相同的問題,並不是所有的錯誤都被記錄下來。 – 2013-03-01 22:42:51

7

爲什麼重新拋出等?這個工作,它會使服務返回狀態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); 
    } 
} 
75

作爲以前答案的補充。

昨天,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); 
    } 
} 
+1

這個工作完美無缺。我記錄和併發處理(因爲我得到了logID並將其傳回,以便用戶可以添加註釋),所以我將Result設置爲一個新的ResponseMessageResult。這一直困擾着我一段時間,謝謝。 – Brett 2014-09-05 23:41:57

+0

這確實有幫助。謝謝! – thebiggestlebowski 2014-12-17 05:38:07