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


Antecedentes

Estoy desarrollando una capa de servicio API para un cliente y se me ha solicitado que capture y registre todos los errores a nivel mundial.

Entonces, mientras que 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 manejados 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 he intentado establecer el atributo [HandleError] globalmente registrando esto filtro:

filters.Add(new HandleErrorAttribute());

Pero eso tampoco registra todos los errores.

Problema / Pregunta

¿Cómo intercepto errores como el generado al llamar a /test arriba para que pueda registrarlos? Parece que esta respuesta debería ser obvia, pero he intentado todo lo que se me ocurre hasta ahora.

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

RESUELTO!

Gracias a Darin Dimitrov, cuya respuesta acepté, conseguí resolver esto. WebAPI hace no manejar los errores de la misma manera que un controlador MVC normal.

Esto es 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 el WebApiConfig clase:

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 solo controlador con el atributo [ExceptionHandling].

Author: Chris Moschini, 2013-03-02

5 answers

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

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

    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;
        }
    }
    
 54
Author: Darin Dimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-15 14:35:31

Como una adición a las respuestas anteriores.

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

Brevemente, agregue registradores de excepciones globales y/o controlador de excepciones globales (solo uno).
Se añaden 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);
  }
}
 75
Author: Vladimir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-21 08:18:53

¿Por qué repensar, etc.? Esto funciona y hará que el estado de devolución del servicio 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);
    }
}
 7
Author: Anders,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-30 11:38:01

¿Ha pensado en hacer algo como un filtro de acción de error de controlador 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 registrar

 2
Author: COLD TOLD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-06 17:22:51

Envuélvalo todo en un try / catch y registre la excepción no manejada, luego pásela. A menos que haya una mejor forma de hacerlo.

Aquí hay una referencia Captura todas las excepciones (manejadas o no manejadas)

(editar: oh API)

 1
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:36