web-dev-qa-db-fra.com

Comment enregistrer TOUTES les exceptions globalement pour une application C # MVC4 WebAPI?

Contexte

Je développe une couche de services API pour un client et il m'a été demandé de détecter et de consigner toutes les erreurs au niveau mondial.

Ainsi, même si quelque chose comme un point de terminaison inconnu (ou une action) est facilement géré en utilisant ELMAH ou en ajoutant quelque chose comme ceci au Global.asax:

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

. . Les erreurs .unhandled qui ne sont pas liées au routage ne sont pas consignées. Par exemple:

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

J'ai également essayé de définir l'attribut [HandleError] globalement en enregistrant ce filtre:

filters.Add(new HandleErrorAttribute());

Mais cela ne consigne pas non plus toutes les erreurs.

Problème/Question

Comment puis-je intercepter des erreurs telles que celle générée en appelant /test ci-dessus afin de pouvoir les enregistrer? Il semble que cette réponse soit évidente, mais j’ai essayé tout ce à quoi je peux penser jusqu’à présent.

Idéalement, je souhaite ajouter des éléments à la journalisation des erreurs, tels que l'adresse IP de l'utilisateur demandeur, la date, l'heure, etc. Je souhaite également pouvoir envoyer automatiquement un courrier électronique au support technique en cas d'erreur. Je peux faire tout cela si seulement je pouvais intercepter ces erreurs quand elles se produisent!

RÉSOLU!

Grâce à Darin Dimitrov, dont j'ai accepté la réponse, j'ai compris ce qui se passait. WebAPI pas gère les erreurs de la même manière qu'un contrôleur MVC classique.

Voici ce qui a fonctionné:

1) Ajoutez un filtre personnalisé à votre espace de noms:

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) Enregistrez maintenant le filtre globalement dans la classe 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());
     }
}

OU vous pouvez ignorer l'enregistrement et simplement décorer un seul contrôleur avec l'attribut [ExceptionHandling].

170
Matt Cashatt

Si votre API Web est hébergée dans une application ASP.NET, l'événement _Application_Error_ sera appelé pour toutes les exceptions non gérées dans votre code, y compris celle de l'action test que vous avez affichée. Il vous suffit donc de gérer cette exception dans l'événement Application_Error. Dans l'exemple de code que vous avez montré, vous ne gérez que l'exception de type HttpException, ce qui n'est évidemment pas le cas du code Convert.ToInt32("a"). Assurez-vous donc que vous vous connectez et gérez toutes les exceptions:

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

La gestion des exceptions dans l'API Web peut être effectuée à différents niveaux. Voici un detailed article expliquant les différentes possibilités:

  • attribut de filtre d'exception personnalisé pouvant être enregistré en tant que filtre d'exception 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"
            });
        }
    }
    _
  • invocateur d'action personnalisée

    _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;
        }
    }
    _
55
Darin Dimitrov

En complément des réponses précédentes.

Hier, l’API Web ASP.NET 2.1 était officiellement publié .
Il offre une autre possibilité de gérer les exceptions au niveau mondial.
Les détails sont donnés dans le échantillon .

En bref, vous ajoutez des enregistreurs d’exception globaux et/ou un gestionnaire d’exception global (un seul).
Vous les ajoutez à la 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());
}

Et leur réalisation:

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

Pourquoi renvoyer etc? Cela fonctionne et le statut de retour de service sera 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);
    }
}
8
Anders

avez-vous pensé à faire quelque chose comme un filtre d'action d'erreur de poignée comme

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

vous pouvez également créer une version personnalisée de [HandleError] avec laquelle vous pouvez écrire des informations d'erreur et tous les autres détails pour vous connecter

2
COLD TOLD

Enveloppez le tout dans un essai/attrape et enregistrez l'exception non gérée, puis transmettez-la. Sauf s'il existe une meilleure manière intégrée de le faire.

Voici une référence Catch All (manipulés ou non gérés) Exceptions

(edit: oh API)

1
Tim