Java Language Création et lecture de stacktraces


Exemple

Lorsqu'un objet d'exception est créée ( par exemple lorsque vous new il), le Throwable constructeur capture d' informations sur le contexte dans lequel l'exception a été créé. Par la suite, ces informations peuvent être générées sous la forme d'un stacktrace, qui peut être utilisé pour diagnostiquer le problème à l'origine de l'exception.

Impression d'un stacktrace

Imprimer une stacktrace consiste simplement à appeler la méthode printStackTrace() . Par exemple:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

La méthode printStackTrace() sans arguments imprimera sur la sortie standard de l'application; c'est-à-dire le System.out actuel. Il existe également des printStackTrace(PrintStream) et des printStackTrace(PrintWriter) qui impriment sur un Stream ou un Writer spécifié.

Remarques:

  1. Le stacktrace n'inclut pas les détails de l'exception elle-même. Vous pouvez utiliser la toString() pour obtenir ces détails. par exemple

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. L'impression Stacktrace doit être utilisée avec parcimonie. voir Piège - Piles superflues ou inappropriées . Il est souvent préférable d'utiliser une infrastructure de journalisation et de passer l'objet exception à journaliser.

Comprendre un stacktrace

Considérons le programme simple suivant composé de deux classes dans deux fichiers. (Nous avons montré les noms de fichiers et les numéros de ligne ajoutés à des fins d’illustration.)

File: "Main.java"
1   public class Main {
2       public static void main(String[] args) {
3           new Test().foo();
4       }
5   }

File: "Test.java"
1   class Test {
2       public void foo() {
3           bar();
4       }
5   
6       public int bar() {
7           int a = 1;
8           int b = 0;
9           return a / b;
10      }

Lorsque ces fichiers sont compilés et exécutés, nous obtiendrons la sortie suivante.

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.bar(Test.java:9)
        at Test.foo(Test.java:3)
        at Main.main(Main.java:3)

Lisons cette ligne à la fois pour comprendre ce qu’elle nous dit.

La ligne n ° 1 nous indique que le thread appelé "main" est terminé en raison d'une exception non interceptée. Le nom complet de l'exception est java.lang.ArithmeticException et le message d'exception est "/ by zero".

Si nous recherchons les javadocs pour cette exception, cela dit:

Lancé lorsqu'une condition arithmétique exceptionnelle s'est produite. Par exemple, un entier "diviser par zéro" lève une instance de cette classe.

En effet, le message "/ by zero" est un indice fort que la cause de l'exception est qu'un code a tenté de diviser quelque chose par zéro. Mais quoi?

Les 3 lignes restantes sont la trace de la pile. Chaque ligne représente un appel de méthode (ou de constructeur) sur la pile d'appels, et chacune nous indique trois choses:

  • le nom de la classe et de la méthode en cours d'exécution,
  • le nom de fichier du code source,
  • le numéro de ligne du code source de l'instruction en cours d'exécution

Ces lignes d'un stacktrace sont répertoriées avec le cadre de l'appel en cours. Le cadre supérieur de notre exemple ci-dessus se trouve dans la méthode Test.bar et à la ligne 9 du fichier Test.java. C'est la ligne suivante:

    return a / b;

Si nous examinons quelques lignes plus tôt dans le fichier où b est initialisé, il est évident que b aura la valeur zéro. Nous pouvons dire sans aucun doute que c'est la cause de l'exception.

Si nous devions aller plus loin, nous pouvons voir à partir du stacktrace que bar() été appelé depuis foo() à la ligne 3 de Test.java, et que foo() été à son tour appelé depuis Main.main() .

Remarque: Les noms de classe et de méthode dans les cadres de pile sont les noms internes des classes et des méthodes. Vous devrez reconnaître les cas inhabituels suivants:

  • Une classe imbriquée ou interne ressemblera à "OuterClass $ InnerClass".
  • Une classe interne anonyme ressemblera à "OuterClass $ 1", "OuterClass $ 2", etc.
  • Lorsque du code dans un constructeur, un initialiseur de champ d'instance ou un bloc d'initialisation d'instance est en cours d'exécution, le nom de la méthode sera "".
  • Lorsque le code d'un initialiseur de champ statique ou d'un bloc d'initialisation statique est en cours d'exécution, le nom de la méthode sera "".

(Dans certaines versions de Java, le code de formatage stacktrace détectera et éliminera les séquences répétées de pile, comme cela peut se produire lorsqu'une application échoue en raison d'une récursivité excessive.)

Chaînage des exceptions et empilements imbriqués

Java SE 1.4

Le chaînage des exceptions se produit lorsqu'un morceau de code intercepte une exception, puis en crée et en lance une nouvelle, en faisant passer la première exception comme cause. Voici un exemple:

File: Test,java
1   public class Test {
2      int foo() {
3           return 0 / 0;
4      }
5
6       public Test() {
7           try {
8               foo();
9           } catch (ArithmeticException ex) {
10              throw new RuntimeException("A bad thing happened", ex);
11          }
12      }
13
14      public static void main(String[] args) {
15          new Test();
16      }
17  }

Lorsque la classe ci-dessus est compilée et exécutée, nous obtenons le stacktrace suivant:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
        at Test.<init>(Test.java:10)
        at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
        at Test.foo(Test.java:3)
        at Test.<init>(Test.java:8)
        ... 1 more

Le stacktrace commence par le nom de la classe, la méthode et la pile d'appels correspondant à l'exception qui (dans ce cas) a provoqué le blocage de l'application. Ceci est suivi par une ligne "Caused by:" qui signale l'exception de cause . Le nom de la classe et le message sont signalés, suivis des cadres de pile de l'exception cause. La trace se termine par un "... N more" qui indique que les N dernières images sont les mêmes que pour l'exception précédente.

Le "Causé par:" est uniquement inclus dans la sortie lorsque la cause l'exception principale n'est pas null . Les exceptions peuvent être chaînées indéfiniment et, dans ce cas, le stacktrace peut avoir plusieurs traces "Caused by:".

Remarque: le mécanisme de cause était uniquement exposé dans l'API Throwable de Java 1.4.0. Avant cela, le chaînage des exceptions devait être implémenté par l'application en utilisant un champ d'exception personnalisé pour représenter la cause et une méthode printStackTrace personnalisée.

Capturer une stacktrace en tant que chaîne

Parfois, une application doit être capable de capturer un stacktrace en tant que String Java, afin de pouvoir l'utiliser à d'autres fins. L'approche générale pour ce faire consiste à créer un OutputStream ou un Writer temporaire qui écrit dans un tampon en mémoire et le transmet à printStackTrace(...) .

Les bibliothèques Apache Commons et Guava fournissent des méthodes utilitaires pour capturer une stacktrace en tant que chaîne:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

Si vous ne pouvez pas utiliser de bibliothèques tierces dans votre base de code, la méthode suivante effectue la tâche:

   /**
     * Returns the string representation of the stack trace.
     *
     * @param throwable the throwable
     * @return the string.
     */
    public static String stackTraceToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

Notez que si vous avez l'intention d'analyser le stacktrace, il est plus simple d'utiliser getStackTrace() et getCause() que d'essayer d'analyser un stacktrace.