intercepter toutes les exceptions non gérées dans l'API Web ASP.NET

113

Comment intercepter toutes les exceptions non gérées qui se produisent dans l'API Web ASP.NET afin de pouvoir les consigner?

Jusqu'à présent, j'ai essayé:

  • Créer et enregistrer un ExceptionHandlingAttribute
  • Implémenter une Application_Errorméthode dansGlobal.asax.cs
  • S'abonner à AppDomain.CurrentDomain.UnhandledException
  • S'abonner à TaskScheduler.UnobservedTaskException

Le ExceptionHandlingAttributegère avec succès les exceptions qui sont levées dans les méthodes d'action du contrôleur et les filtres d'action, mais les autres exceptions ne sont pas gérées, par exemple:

  • Exceptions levées lorsqu'un IQueryableretourné par une méthode d'action échoue à s'exécuter
  • Exceptions levées par un gestionnaire de messages (ie HttpConfiguration.MessageHandlers)
  • Exceptions levées lors de la création d'une instance de contrôleur

Fondamentalement, si une exception va provoquer le renvoi d'une erreur de serveur interne 500 au client, je veux qu'elle soit enregistrée. La mise en œuvre Application_Errora bien fonctionné dans Web Forms et MVC - que puis-je utiliser dans Web Api?

Joe Daley
la source
Avez-vous essayé d'utiliser la surveillance de l'état d'ASP.NET ? Activez-le simplement et voyez si vos exceptions ne sont pas enregistrées dans le journal des événements.
John Saunders
La surveillance de l'état détecte mes exceptions de pipeline MVC, mais pas mes exceptions de pipeline Web Api.
Joe Daley
Merci - Il m'a fallu un certain temps pour comprendre pourquoi je ne pouvais pas enregistrer mes problèmes d'injection de constructeur / dépendance, où je pensais que la journalisation WebAPI était déjà triée ...
Survol du

Réponses:

156

Ceci est maintenant possible avec WebAPI 2.1 (voir Quoi de neuf ):

Créez une ou plusieurs implémentations de IExceptionLogger. Par exemple:

public class TraceExceptionLogger : ExceptionLogger
{
    public override void Log(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

Ensuite, inscrivez-vous avec HttpConfiguration de votre application, dans un rappel de configuration comme ceci:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

ou directement:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());
décates
la source
7
@NeilBarnwell Oui, l'API Web 2.1 correspond à l'assembly System.Web.Http version 5.1.0 . Vous avez donc besoin de cette version ou supérieure pour utiliser la solution décrite ici. Voir les versions du package nuget
décates le
2
Certaines 500 erreurs ne sont toujours pas prises en compte, par exemple. HttpException - l'hôte distant a fermé la connexion. Existe-t-il encore une place pour global.asax Application_Error pour gérer les erreurs en dehors du traitement de l'API Web?
Avner
11
J'adore à quel point le document officiel est détaillé sur msdn et ce que 99% des développeurs veulent vraiment, ce sont juste les 8 lignes de code pour enregistrer les erreurs.
Rocklan
20

La réponse de Yuval est de personnaliser les réponses aux exceptions non gérées capturées par l'API Web, pas pour la journalisation, comme indiqué sur la page liée . Reportez-vous à la section Quand utiliser sur la page pour plus de détails. L'enregistreur est toujours appelé mais le gestionnaire n'est appelé que lorsqu'une réponse peut être envoyée. En bref, utilisez le logger pour enregistrer et le gestionnaire pour personnaliser la réponse.

Au fait, j'utilise l'assembly v5.2.3 et la ExceptionHandlerclasse n'a pas la HandleCoreméthode. L'équivalent, je pense, est Handle. Cependant, le simple sous-classement ExceptionHandler(comme dans la réponse de Yuval) ne fonctionne pas. Dans mon cas, je dois mettre IExceptionHandleren œuvre comme suit.

internal class OopsExceptionHandler : IExceptionHandler
{
    private readonly IExceptionHandler _innerHandler;

    public OopsExceptionHandler (IExceptionHandler innerHandler)
    {
        if (innerHandler == null)
            throw new ArgumentNullException(nameof(innerHandler));

        _innerHandler = innerHandler;
    }

    public IExceptionHandler InnerHandler
    {
        get { return _innerHandler; }
    }

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
        Handle(context);

        return Task.FromResult<object>(null);
    }

    public void Handle(ExceptionHandlerContext context)
    {
        // Create your own custom result here...
        // In dev, you might want to null out the result
        // to display the YSOD.
        // context.Result = null;
        context.Result = new InternalServerErrorResult(context.Request);
    }
}

Notez que, contrairement à l'enregistreur, vous enregistrez votre gestionnaire en remplaçant le gestionnaire par défaut et non en ajoutant.

config.Services.Replace(typeof(IExceptionHandler),
    new OopsExceptionHandler(config.Services.GetExceptionHandler()));
Duoc Tran
la source
1
C'est une excellente solution, cela devrait être la solution acceptée pour «attraper ou enregistrer toutes les erreurs». Je n'aurais jamais pu comprendre pourquoi cela ne fonctionnait pas pour moi alors que je ne faisais qu'étendre ExceptionHandler.
Rajiv
Excellente solution. Une fois que le pipeline MVC a été chargé pour une requête, cela fonctionne très bien. IIS gère toujours les exceptions jusque-là, y compris lors de la rotation d'OWIN dans startup.cs. Cependant, à un moment donné, après que le spin up ait terminé le traitement de startup.cs, il fait son travail à merveille.
Gustyn le
18

Pour répondre à ma propre question, ce n'est pas possible!

La gestion de toutes les exceptions qui provoquent des erreurs de serveur internes semble être une capacité de base que l'API Web devrait avoir, j'ai donc demandé à Microsoft un gestionnaire d'erreurs global pour l'API Web :

https://aspnetwebstack.codeplex.com/workitem/1001

Si vous êtes d'accord, accédez à ce lien et votez pour lui!

En attendant, l'excellent article Gestion des exceptions de l'API Web ASP.NET montre différentes façons de détecter quelques catégories d'erreur différentes. C'est plus compliqué qu'il ne devrait l'être, et cela ne détecte pas toutes les erreurs de serveur internes, mais c'est la meilleure approche disponible aujourd'hui.

Mise à jour: la gestion globale des erreurs est désormais implémentée et disponible dans les versions nocturnes! Il sera publié dans ASP.NET MVC v5.1. Voici comment cela fonctionnera: https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling

Joe Daley
la source
semble être une raison d'utiliser le contrôleur pour les appels ajax au lieu de l'api Web .. les limites sont déjà floues .. bien que si ELMAH est capable de le capturer, il y a peut-être un moyen
Sonic Soul
4
La gestion globale des erreurs a maintenant été ajoutée dans l'API Web 2.1. Voir ma réponse pour plus de détails.
décate le
10

Vous pouvez également créer un gestionnaire d'exceptions global en implémentant l' IExceptionHandlerinterface (ou en héritant de la ExceptionHandlerclasse de base). Ce sera le dernier à être appelé dans la chaîne d'exécution, après tout enregistré IExceptionLogger:

Le IExceptionHandler gère toutes les exceptions non gérées de tous les contrôleurs. C'est le dernier de la liste. Si une exception se produit, le IExceptionLogger sera appelé en premier, puis le contrôleur ExceptionFilters et, s'il n'est toujours pas géré, l'implémentation IExceptionHandler.

public class OopsExceptionHandler : ExceptionHandler
{
    public override void HandleCore(ExceptionHandlerContext context)
    {
        context.Result = new TextPlainErrorResult
        {
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong."        
        };
    }

    private class TextPlainErrorResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

Plus à ce sujet ici .

Yuval Itzchakov
la source
Juste curieux, où est-ce enregistré?
ThunD3eR
-1

Vous pouvez avoir des blocs try-catch existants dont vous n'êtes pas au courant.

Je pensais que ma nouvelle global.asax.Application_Errorméthode n'était pas systématiquement appelée pour des exceptions non gérées dans notre ancien code.

Ensuite, j'ai trouvé quelques blocs try-catch au milieu de la pile d'appels qui ont appelé Response.Write sur le texte d'exception. C'était ça. J'ai vidé le texte sur l'écran, puis tué la pierre d'exception.

Les exceptions étaient donc gérées, mais la manipulation ne faisait rien d'utile. Une fois que j'ai supprimé ces blocs try-catch, les exceptions se sont propagées à la méthode Application_Error comme prévu.

Ressource
la source