¿Cómo registro TODAS las excepciones globalmente para una aplicación C # MVC4 WebAPI?

Fondo

Estoy desarrollando una capa de servicio de API para un cliente y se me ha pedido que capture y registre todos los errores de forma global.

Entonces, si algo como un punto final desconocido (o acción) se maneja fácilmente usando ELMAH o agregando algo como esto a Global.asax :

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

. . . Los errores no controlados que no están relacionados con el enrutamiento no se registran. Por ejemplo:

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

También intenté configurar el atributo [HandleError] globalmente al registrar este filtro:

 filters.Add(new HandleErrorAttribute()); 

Pero eso tampoco registra todos los errores.

Problema / Pregunta

¿Cómo puedo interceptar errores como el generado al llamar /test arriba para poder registrarlos? Parece que esta respuesta debería ser obvia, pero he intentado todo lo que puedo pensar hasta ahora.

Idealmente, deseo agregar algunas cosas al registro de errores, como la dirección IP del usuario solicitante, la fecha, la hora, etc. También deseo poder enviar un correo electrónico al personal de soporte automáticamente cuando se encuentre un error. ¡Todo esto puedo hacer si solo puedo interceptar estos errores cuando ocurren!

¡RESUELTO!

Gracias a Darin Dimitrov, cuya respuesta acepté, entendí esto. WebAPI no maneja los errores de la misma manera que un controlador MVC regular.

Aquí está lo que funcionó:

1) Agregue un filtro personalizado a su espacio de nombres:

 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) Ahora registre el filtro globalmente en la clase 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()); } } 

O puede omitir el registro y simplemente decorar un único controlador con el atributo [ExceptionHandling] .

Si su API web está alojada dentro de una aplicación ASP.NET, se llamará al evento Application_Error para todas las excepciones no controladas en su código, incluida la de la acción de prueba que ha mostrado. Entonces, todo lo que tiene que hacer es manejar esta excepción dentro del evento Application_Error. En el código de muestra que ha mostrado, solo está manejando la excepción del tipo HttpException que obviamente no es el caso con el Convert.ToInt32("a") . Así que asegúrese de registrar y manejar todas las excepciones allí:

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

El manejo de excepciones en la API web podría hacerse en varios niveles. Aquí hay un detailed article explica las diferentes posibilidades:

  • atributo de filtro de excepción personalizado que podría registrarse como un filtro de excepción global

     [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" }); } } 
  • invitante de acción personalizada

     public class MyApiControllerActionInvoker : ApiControllerActionInvoker { public override Task 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(() => new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(baseException.Message), ReasonPhrase = "Error" }); } else { //Log critical error Debug.WriteLine(baseException); return Task.Run(() => new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(baseException.Message), ReasonPhrase = "Critical Error" }); } } return result; } } 

Como una adición a las respuestas anteriores.

Ayer, ASP.NET Web API 2.1 fue lanzado oficialmente.
Ofrece otra oportunidad para manejar excepciones globalmente.
Los detalles se dan en la muestra .

En resumen, agrega registradores de excepciones globales y / o manejador de excepciones globales (solo uno).
Usted los agrega a la configuración:

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

Y su realización:

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

¿Por qué volver a tirar, etc.? Esto funciona y hará que el servicio devuelva el estado 500, etc.

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

¿Has pensado en hacer algo así como un filtro de acción de error de manejo como

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

también puede crear una versión personalizada de [HandleError] con la que puede escribir información de error y todos los demás detalles para iniciar sesión

Envuelva todo en un try / catch y registre la excepción no controlada, luego páselo. A menos que haya una mejor forma integrada de hacerlo.

Aquí hay una referencia Capturar todas (manejadas o no) Excepciones

(editar: oh API)