web-dev-qa-db-fra.com

Comment enregistrer autant d'informations que possible pour une exception Java?

Il y a un problème commun que j'ai rencontré à quelques reprises lors de la journalisation des exceptions. Il semble y avoir différents types à traiter. Par exemple. certaines enveloppent d'autres exceptions, d'autres n'ont pas du tout de message - juste un type.

La plupart du code que j'ai vu consignait des exceptions en utilisant getMessage() ou toString(), Mais celles-ci ne capturaient pas toujours toutes les informations nécessaires pour identifier le problème - d'autres méthodes telles que getCause() et getStackTrace() fournissent parfois des informations supplémentaires.

À titre d'exemple, l'exception que je regarde actuellement dans ma fenêtre Eclipse Inspect est un InvocationTargetException. L'exception elle-même n'a pas de cause, pas de message, pas de stacktrace ... mais la cible de getCause () est InvalidUseOfMatchersException avec ces informations renseignées.

Ma question est donc la suivante: étant donné une exception de type tout type en tant qu’entrée, veuillez fournir une seule méthode qui générera une chaîne bien formatée contenant tout des informations pertinentes sur l’exception (par exemple, en appelant récursivement getCause(), entre autres?) Avant de poser cette question, j'allais presque tenter ma chance moi-même, mais je ne voulais pas vraiment réinventer la roue - une telle chose a sûrement déjà été faite de nombreuses fois auparavant ...?

Merci de ne pas me diriger vers une structure de journalisation ou d’utilitaire particulière. Je cherche un fragment de code plutôt qu’une bibliothèque car je n’ai pas le droit d’ajouter des dépendances externes au projet. I travaille et je suis connecté à une partie d'une page Web plutôt qu'à un fichier. S'il s'agit de copier le fragment de code hors d'un tel cadre (et de l'attribuer), c'est très bien: -)

53
Steve Chambers

Le Java.util.logging package est standard dans Java SE . Son Logger inclut une méthode de journal surchargée qui accepte les objets Throwable . Il enregistrera les empilements d'exceptions et leur cause pour vous.

Par exemple:

import Java.util.logging.Level;
import Java.util.logging.Logger;

[...]

Logger logger = Logger.getAnonymousLogger();
Exception e1 = new Exception();
Exception e2 = new Exception(e1);
logger.log(Level.SEVERE, "an exception was thrown", e2);

Se connecter:

SEVERE: an exception was thrown
Java.lang.Exception: Java.lang.Exception
    at LogStacktrace.main(LogStacktrace.Java:21)
Caused by: Java.lang.Exception
    at LogStacktrace.main(LogStacktrace.Java:20)

En interne, cela correspond exactement à ce que @ philipp-wendler suggère d'ailleurs. Voir le code source pour SimpleFormatter.Java . Ceci est juste une interface de niveau supérieur.

69
flup

Quel est le problème avec la méthode printStacktrace() fournie par Throwable (et donc chaque exception)? Il affiche toutes les informations demandées, y compris le type, le message et la trace de pile de l'exception racine et toutes les causes (imbriquées). Dans Java 7, il vous montre même les informations sur les exceptions "supprimées" susceptibles de se produire dans une instruction try-with-resources.

Bien sûr, vous ne voudriez pas écrire dans System.err, Comme le fait la version sans argument de la méthode, utilisez donc l'une des surcharges disponibles.

En particulier, si vous voulez juste obtenir une chaîne:

  Exception e = ...
  StringWriter sw = new StringWriter();
  e.printStackTrace(new PrintWriter(sw));
  String exceptionDetails = sw.toString();

Si vous utilisez la grande bibliothèque Guava , elle fournit une méthode utilitaire faisant ceci: com.google.common.base.Throwables#getStackTraceAsString(Throwable) .

19
Philipp Wendler

Cela devrait être assez simple si vous utilisez LogBack ou SLF4J. Je le fais comme ci-dessous

//imports
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//Initialize logger
Logger logger = LoggerFactory.getLogger(<classname>.class);
try {
   //try something
} catch(Exception e){
   //Actual logging of error
   logger.error("some message", e);
}
11

Un script de journalisation que j'ai écrit il y a quelque temps peut être utile, bien que ce ne soit pas exactement ce que vous voulez. Il agit comme un System.out.println mais avec beaucoup plus d'informations sur StackTrace, etc. Il fournit également du texte cliquable pour Eclipse:

private static final SimpleDateFormat   extended    = new SimpleDateFormat( "dd MMM yyyy (HH:mm:ss) zz" );

public static Java.util.logging.Logger initLogger(final String name) {
    final Java.util.logging.Logger logger = Java.util.logging.Logger.getLogger( name );
    try {

        Handler ch = new ConsoleHandler();
        logger.addHandler( ch );

        logger.setLevel( Level.ALL ); // Level selbst setzen

        logger.setUseParentHandlers( false );

        final Java.util.logging.SimpleFormatter formatter = new SimpleFormatter() {

            @Override
            public synchronized String format(final LogRecord record) {
                StackTraceElement[] trace = new Throwable().getStackTrace();
                String clickable = "(" + trace[ 7 ].getFileName() + ":" + trace[ 7 ].getLineNumber() + ") ";
                /* Clickable text in Console. */

                for( int i = 8; i < trace.length; i++ ) {
                    /* 0 - 6 is the logging trace, 7 - x is the trace until log method was called */
                    if( trace[ i ].getFileName() == null )
                        continue;
                    clickable = "(" + trace[ i ].getFileName() + ":" + trace[ i ].getLineNumber() + ") -> " + clickable;
                }

                final String time = "<" + extended.format( new Date( record.getMillis() ) ) + "> ";

                StringBuilder level = new StringBuilder("[" + record.getLevel() + "] ");
                while( level.length() < 15 ) /* extend for tabby display */
                    level.append(" ");

                StringBuilder name = new StringBuilder(record.getLoggerName()).append(": ");
                while( name.length() < 15 ) /* extend for tabby display */
                    name.append(" ");

                String thread = Thread.currentThread().getName();
                if( thread.length() > 18 ) /* trim if too long */
                    thread = thread.substring( 0, 16 ) + "..."; 
                else {
                    StringBuilder sb = new StringBuilder(thread);
                    while( sb.length() < 18 ) /* extend for tabby display */
                        sb.append(" ");
                    thread = sb.insert( 0, "Thread " ).toString();
                }

                final String message = "\"" + record.getMessage() + "\" ";

                return level + time + thread + name + clickable + message + "\n";
            }
        };
        ch.setFormatter( formatter );
        ch.setLevel( Level.ALL );
    } catch( final SecurityException e ) {
        e.printStackTrace();
    }
    return logger;
}

Notez que ces sorties sur la console, vous pouvez changer cela, voir http://docs.Oracle.com/javase/1.4.2/docs/api/Java/util/logging/Logger.html pour plus d'informations à ce sujet.

Maintenant, ce qui suit fera probablement ce que vous voulez. Il passera en revue toutes les causes d'un Throwable et le sauvegardera dans une chaîne. Notez que ceci n’utilise pas StringBuilder, vous pouvez donc l’optimiser en le modifiant.

Throwable e = ...
String detail = e.getClass().getName() + ": " + e.getMessage();
for( final StackTraceElement s : e.getStackTrace() )
    detail += "\n\t" + s.toString();
while( ( e = e.getCause() ) != null ) {
    detail += "\nCaused by: ";
    for( final StackTraceElement s : e.getStackTrace() )
        detail += "\n\t" + s.toString();
}

Cordialement,
Danyel

1
Danyel

Quelque chose que je fasse est d'avoir une méthode statique qui gère toutes les exceptions et j'ajoute le journal à un JOptionPane pour le montrer à l'utilisateur, mais vous pouvez écrire le résultat dans un fichier dans FileWriter enveloppé dans un BufeeredWriter. Pour la méthode statique principale, pour attraper les exceptions non récupérées, je le fais:

SwingUtilities.invokeLater( new Runnable() {
    @Override
    public void run() {
        //Initializations...
    }
});


Thread.setDefaultUncaughtExceptionHandler( 
    new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException( Thread t, Throwable ex ) {
            handleExceptions( ex, true );
        }
    }
);

Et quant à la méthode:

public static void handleExceptions( Throwable ex, boolean shutDown ) {
    JOptionPane.showMessageDialog( null,
        "A CRITICAL ERROR APPENED!\n",
        "SYSTEM FAIL",
        JOptionPane.ERROR_MESSAGE );

    StringBuilder sb = new StringBuilder(ex.toString());
    for (StackTraceElement ste : ex.getStackTrace()) {
        sb.append("\n\tat ").append(ste);
    }


    while( (ex = ex.getCause()) != null ) {
        sb.append("\n");
        for (StackTraceElement ste : ex.getStackTrace()) {
            sb.append("\n\tat ").append(ste);
        }
    }

    String trace = sb.toString();

    JOptionPane.showMessageDialog( null,
        "PLEASE SEND ME THIS ERROR SO THAT I CAN FIX IT. \n\n" + trace,
        "SYSTEM FAIL",
        JOptionPane.ERROR_MESSAGE);

    if( shutDown ) {
        Runtime.getRuntime().exit( 0 );
    }
}

Dans votre cas, au lieu de "crier" à l'utilisateur, vous pourriez écrire un journal, comme je vous l'ai déjà dit:

String trace = sb.toString();

File file = new File("mylog.txt");
FileWriter myFileWriter = null;
BufferedWriter myBufferedWriter = null;

try {
    //with FileWriter(File file, boolean append) you can writer to 
    //the end of the file
    myFileWriter = new FileWriter( file, true );
    myBufferedWriter = new BufferedWriter( myFileWriter );

    myBufferedWriter.write( trace );
}
catch ( IOException ex1 ) {
    //Do as you want. Do you want to use recursive to handle 
    //this exception? I don't advise that. Trust me...
}
finally {
    try {
        myBufferedWriter.close();
    }
    catch ( IOException ex1 ) {
        //Idem...
    }

    try {
        myFileWriter.close();
    }
    catch ( IOException ex1 ) {
        //Idem...
    }
}

J'espère avoir aidé.

Bonne journée. :)

1
Saclyr Barlonium

Vous pouvez également utiliser Apache ExceptionUtils.

Exemple:

import org.Apache.commons.lang.exception.ExceptionUtils;
import org.Apache.log4j.Logger;


public class Test {

    static Logger logger = Logger.getLogger(Test.class);

    public static void main(String[] args) {

        try{
            String[] avengers = null;
            System.out.println("Size: "+avengers.length);
        } catch (NullPointerException e){
            logger.info(ExceptionUtils.getFullStackTrace(e));
        }
    }

}

Sortie de la console:

Java.lang.NullPointerException
    at com.aimlessfist.avengers.ironman.Test.main(Test.Java:11)
1
RIP_SunMicroSys