web-dev-qa-db-fra.com

Qu'est-ce qu'une trace de pile et comment puis-je l'utiliser pour déboguer mes erreurs d'application?

Parfois, lorsque je lance mon application, cela me donne une erreur qui ressemble à:

Exception in thread "main" Java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.Java:16)
        at com.example.myproject.Author.getBookTitles(Author.Java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)

Les gens ont appelé cela une "trace de pile". Qu'est-ce qu'une trace de pile? Qu'est-ce que cela peut me dire sur l'erreur qui se produit dans mon programme?


À propos de cette question - très souvent, je vois une question à laquelle un programmeur novice "obtient une erreur", et il lui suffit de coller sa trace de pile et un bloc de code aléatoire sans comprendre ce que la pile trace est ou comment ils peuvent l'utiliser. Cette question est destinée à servir de référence aux programmeurs débutants qui pourraient avoir besoin d'aide pour comprendre la valeur d'une trace de pile.

608
Rob Hruska

En termes simples, trace de pile est une liste des appels de méthode que l'application était au milieu du moment où une exception a été générée.

Exemple simple

Avec l'exemple donné dans la question, nous pouvons déterminer exactement où l'exception a été levée dans l'application. Regardons la trace de la pile:

Exception in thread "main" Java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.Java:16)
        at com.example.myproject.Author.getBookTitles(Author.Java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)

C'est une trace de pile très simple. Si nous commençons au début de la liste de "at ...", nous pouvons dire où notre erreur s'est produite. Ce que nous recherchons, c'est l'appel à la méthode topmost qui fait partie de notre application. Dans ce cas, c'est:

at com.example.myproject.Book.getTitle(Book.Java:16)

Pour déboguer ceci, nous pouvons ouvrir Book.Java et regarder la ligne 16, qui est:

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

Cela indiquerait que quelque chose (probablement title) est null dans le code ci-dessus.

Exemple avec une chaîne d'exceptions

Parfois, les applications interceptent une exception et la rejettent comme cause d'une autre exception. Cela ressemble typiquement à:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 22
37      } catch (NullPointerException e) {
38         throw new IllegalStateException("A book has a null property", e)
39      }
40   }

Cela pourrait vous donner une trace de pile qui ressemble à:

Exception in thread "main" Java.lang.IllegalStateException: A book has a null property
        at com.example.myproject.Author.getBookIds(Author.Java:38)
        at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)
Caused by: Java.lang.NullPointerException
        at com.example.myproject.Book.getId(Book.Java:22)
        at com.example.myproject.Author.getBookIds(Author.Java:36)
        ... 1 more

Ce qui est différent de celui-ci est le "Caused by". Parfois, les exceptions auront plusieurs sections "Caused by". Pour ceux-ci, vous souhaitez généralement rechercher la "cause première", qui sera l'une des sections "Caused by" (Causée par) les plus basses de la trace de pile. Dans notre cas, c'est:

Caused by: Java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.Java:22) <-- important line

Encore une fois, avec cette exception, nous voudrions regarder la ligne 22 de Book.Java pour voir ce qui pourrait causer la NullPointerException ici.

Exemple plus intimidant avec le code de bibliothèque

Les traces de pile sont généralement beaucoup plus complexes que les deux exemples ci-dessus. Voici un exemple (il est long, mais présente plusieurs niveaux d'exceptions chaînées):

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.Java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1157)
    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.Java:28)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.Java:33)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.Java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.Java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.Java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.Java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.Java:418)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.Java:152)
    at org.mortbay.jetty.Server.handle(Server.Java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.Java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.Java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.Java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.Java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.Java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.Java:228)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.Java:582)
Caused by: com.example.myproject.MyProjectServletException
    at com.example.myproject.MyServlet.doPost(MyServlet.Java:169)
    at javax.servlet.http.HttpServlet.service(HttpServlet.Java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.Java:820)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.Java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.Java:1166)
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.Java:30)
    ... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.Java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.Java:66)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.Java:64)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.Java:2329)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.Java:2822)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.Java:71)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.Java:268)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.Java:321)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.Java:204)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.Java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.Java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.Java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.Java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.Java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.Java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.Java:705)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.Java:693)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.Java:689)
    at Sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:25)
    at Java.lang.reflect.Method.invoke(Method.Java:597)
    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.Java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.Java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.Java:164)
    ... 32 more
Caused by: Java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.Java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.Java:57)
    ... 54 more

Dans cet exemple, il y a beaucoup plus. Ce qui nous préoccupe le plus, c'est de rechercher des méthodes qui proviennent de notre code , ce qui correspond à tout ce qui se trouve dans le package com.example.myproject. Dans le deuxième exemple (ci-dessus), nous souhaitons d’abord rechercher la cause première, à savoir:

Caused by: Java.sql.SQLException

Cependant, tous les appels de méthode sous cela sont du code de bibliothèque. Nous allons donc passer au-dessus de "Caused by", et rechercher le premier appel de méthode provenant de notre code, qui est:

at com.example.myproject.MyEntityService.save(MyEntityService.Java:59)

Comme dans les exemples précédents, nous devrions regarder MyEntityService.Java en ligne 59, car c’est de là que provient cette erreur (c’est un peu évident ce qui a mal tourné, puisque SQLException énonce l’erreur, mais la procédure de débogage est ce que nous sommes après).

556
Rob Hruska

Je publie cette réponse afin que la première réponse (triée par activité) ne soit pas tout simplement fausse.

Qu'est-ce qu'un Stacktrace?

Un stacktrace est un outil de débogage très utile. Il affiche la pile d'appels (c'est-à-dire la pile de fonctions appelées jusque-là) au moment où une exception non interceptée a été levée (ou l'heure à laquelle le tracé de pile a été généré manuellement). Ceci est très utile car cela ne vous indique pas seulement où l'erreur s'est produite, mais aussi comment le programme s'est retrouvé à cet endroit du code. Ceci nous amène à la question suivante:

Qu'est-ce qu'une exception?

Une exception est ce que l'environnement d'exécution utilise pour vous informer qu'une erreur s'est produite. Des exemples populaires sont NullPointerException, IndexOutOfBoundsException ou ArithmeticException. Chacune de celles-ci est provoquée lorsque vous essayez de faire quelque chose qui n'est pas possible. Par exemple, une exception NullPointerException sera levée lorsque vous essayez de déréférencer un objet Null:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

Comment dois-je gérer les Stacktraces/Exceptions?

En premier lieu, déterminez la cause de l'exception. Essayez de rechercher le nom de l'exception sur Google afin de déterminer la cause de cette exception. La plupart du temps, cela sera causé par un code incorrect. Dans les exemples donnés ci-dessus, toutes les exceptions sont causées par un code incorrect. Ainsi, pour l'exemple NullPointerException, vous pouvez vous assurer que a n'est jamais nul à ce moment-là. Vous pouvez, par exemple, initialiser a ou inclure un contrôle comme celui-ci:

if (a!=null) {
    a.toString();
}

De cette façon, la ligne incriminée n'est pas exécutée si a==null. Il en va de même pour les autres exemples.

Parfois, vous ne pouvez pas vous assurer que vous n'obtenez pas une exception. Par exemple, si vous utilisez une connexion réseau dans votre programme, vous ne pouvez pas empêcher l'ordinateur de perdre sa connexion Internet (par exemple, vous ne pouvez pas empêcher l'utilisateur de déconnecter la connexion réseau de l'ordinateur). Dans ce cas, la bibliothèque réseau lèvera probablement une exception. Maintenant, vous devriez attraper l'exception et handle elle. Cela signifie que, dans l'exemple avec la connexion réseau, vous devriez essayer de rouvrir la connexion ou informer l'utilisateur ou quelque chose comme ça. De même, chaque fois que vous utilisez catch, attrapez toujours uniquement l'exception que vous souhaitez intercepter, n'utilisez pas d'instructions catch telles que catch (Exception e) qui intercepteraient toutes les exceptions. Ceci est très important, car sinon, vous pourriez accidentellement intercepter la mauvaise exception et réagir de manière erronée.

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

Pourquoi ne devrais-je pas utiliser catch (Exception e)?

Prenons un petit exemple pour montrer pourquoi vous ne devez pas capturer toutes les exceptions:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

Ce code essaie de capturer la ArithmeticException causée par une division possible par 0. Mais il capture également une possible NullPointerException qui est levée si a ou b sont null. Cela signifie que vous pouvez obtenir un NullPointerException mais que vous le traiterez comme une exception arithmétique et que vous ferez probablement la mauvaise chose. Dans le meilleur des cas, il vous manque encore une exception NullPointerException. Ce genre de choses rend le débogage beaucoup plus difficile, alors ne le faites pas.

TLDR

  1. Déterminez quelle est la cause de l'exception et corrigez-la afin qu'elle ne lève pas l'exception du tout.
  2. Si 1. n'est pas possible, capturez l'exception spécifique et gérez-la.

    • Ne jamais ajouter simplement un essai/attraper et ensuite ignorer l'exception! Ne fais pas ça!
    • N'utilisez jamais catch (Exception e), attrapez toujours des exceptions spécifiques. Cela vous évitera beaucoup de maux de tête.
73
Dakkaron

Pour ajouter à ce que Rob a mentionné. La définition de points de rupture dans votre application permet le traitement pas à pas de la pile. Cela permet au développeur d’utiliser le débogueur pour voir à quel moment précis la méthode fait quelque chose d’imprévu.

Puisque Rob a utilisé la NullPointerException (NPE) pour illustrer quelque chose de commun, nous pouvons vous aider à résoudre ce problème de la manière suivante:

si nous avons une méthode qui prend des paramètres tels que: void (String firstName)

Dans notre code, nous voudrions évaluer que firstName contient une valeur, nous le ferions comme suit: if(firstName == null || firstName.equals("")) return;

Ce qui précède nous empêche d’utiliser firstName comme paramètre non sécurisé. Par conséquent, en effectuant des contrôles nuls avant le traitement, nous pouvons aider à garantir que notre code fonctionnera correctement. Pour développer un exemple qui utilise un objet avec des méthodes, vous pouvez regarder ici:

if(dog == null || dog.firstName == null) return;

Ce qui précède correspond à l'ordre approprié pour vérifier les valeurs NULL. Nous commençons par l'objet de base, chien dans ce cas, puis nous commençons à parcourir l'arbre des possibilités pour nous assurer que tout est valide avant le traitement. Si l'ordre était inversé, un NPE pourrait potentiellement être lancé et notre programme se bloquerait.

21
Woot4Moo

Il existe une dernière fonctionnalité de pile offerte par la famille Throwable - la possibilité de manipuler informations de trace de pile.

Comportement standard:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Trace de la pile:

Exception in thread "main" Java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.Java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.Java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.Java:9)
    at test.stack.trace.SomeClass.main(SomeClass.Java:27)

Trace de pile manipulée:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.Java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.Java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

Trace de la pile:

Exception in thread "main" Java.lang.RuntimeException
    at OtherClass.methodX(String.Java:99)
    at OtherClass.methodY(String.Java:55)
15
przemek hertel

Pour comprendre le nom : Une trace de pile est une liste d'exceptions (ou vous pouvez dire une liste de "Cause par"), à partir de l'exception la plus superficielle. (par exemple, l’exception de couche de service) jusqu’au niveau le plus profond (par exemple, l’exception de base de données). Tout comme la raison pour laquelle nous l'appelons "pile" est parce que pile est First in Last Out (FILO), l'exception la plus profonde a eu lieu au tout début, puis une chaîne d'exceptions a été générée, une série de conséquences, l'exception de surface a été la dernière. l'un est arrivé à temps, mais nous le voyons en premier lieu.

Touche 1 : Une chose importante et délicate à comprendre ici est la suivante: la cause la plus profonde peut ne pas être la "cause première", car si vous en écrivez "mauvais code", il peut provoquer une exception en dessous qui est plus profonde que sa couche. Par exemple, une requête SQL incorrecte peut entraîner la réinitialisation de la connexion SQLServerException dans le bottem au lieu d'une erreur syndax, qui peut se situer au milieu de la pile.

-> Localisez la cause centrale de votre travail. enter image description here

Touche 2 : Une autre chose délicate mais importante se trouve à l'intérieur de chaque bloc "Cause par", la première ligne était la couche la plus profonde et arrivait à la première place pour ce bloc. Par exemple,

Exception in thread "main" Java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.Java:16)
           at com.example.myproject.Author.getBookTitles(Author.Java:25)
               at com.example.myproject.Bootstrap.main(Bootstrap.Java:14)

Book.Java:16 a été appelé par Auther.Java:25, appelé par Bootstrap.Java:14, Book.Java:16 était à l'origine du problème. Joignez ici un diagramme triant la pile de traces par ordre chronologique. enter image description here

15
Kevin Li

Juste pour ajouter aux autres exemples, il y a classes internes (imbriquées) qui apparaissent avec le signe $. Par exemple:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

Entraînera cette trace de pile:

Exception in thread "main" Java.lang.RuntimeException
        at Test.privateMethod(Test.Java:4)
        at Test.access$000(Test.Java:1)
        at Test$1.run(Test.Java:10)
        at Test.main(Test.Java:13)
8
Eugene S

Les autres publications décrivent ce qu'est une trace de pile, mais il peut rester difficile de travailler avec.

Si vous obtenez une trace de la pile et souhaitez rechercher la cause de l'exception, vous devez commencer par utiliser la console de la pile de trace Java dans Eclipse. Si vous utilisez un autre IDE, il peut y avoir une fonctionnalité similaire, mais cette réponse concerne Eclipse.

Tout d’abord, assurez-vous que vous avez toutes vos sources Java accessibles dans un projet Eclipse.

Ensuite, dans la perspective Java , cliquez sur l’onglet Console (généralement à le fond). Si la vue Console n’est pas visible, sélectionnez l’option de menu Fenêtre -> Afficher la vue et sélectionnez Console .

Puis dans la fenêtre de la console, cliquez sur le bouton suivant (à droite)

Consoles button

puis sélectionnez Console Java Stack Trace dans la liste déroulante.

Collez votre trace de pile dans la console. Il fournira ensuite une liste de liens dans votre code source et tout autre code source disponible.

Voici ce que vous pourriez voir (image de la documentation Eclipse):

Diagram from Eclipse documentation

L'appel de méthode le plus récent sera le sommet de la pile, qui correspond à la ligne du haut (à l'exclusion du texte du message). Descendre dans la pile remonte dans le temps. La deuxième ligne est la méthode qui appelle la première ligne, etc.

Si vous utilisez un logiciel open source, vous devrez peut-être télécharger et attacher à votre projet les sources si vous souhaitez les examiner. Téléchargez les fichiers source dans votre projet et ouvrez le dossier Bibliothèques référencées afin de trouver votre fichier jar pour votre module open-source (celui contenant les fichiers de classe). puis faites un clic droit, sélectionnez Propriétés et attachez le fichier jar source.

5
rghome