¿Cómo registrar excepciones en Java?

8 minutos de lectura

Avatar de usuario de Steve Chambers
steve cámaras

Hay un problema común con el que me he encontrado varias veces al registrar excepciones en Java. Parece que hay varios tipos diferentes para tratar. Por ejemplo, algunos envuelven otras excepciones y otros no tienen ningún mensaje, solo un tipo.

La mayoría de los códigos que he visto registran una excepción usando cualquiera getMessage() o toString(). Pero estos métodos no siempre capturan toda la información necesaria para identificar el problema; otros métodos como getCause() y getStackTrace() a veces proporcionan información adicional.

Por ejemplo, la excepción que estoy viendo en este momento en mi ventana de Eclipse Inspect es una InvocationTargetException. La excepción en sí no tiene causa, ni mensaje, ni stacktrace… pero el objetivo de getCause() es InvalidUseOfMatchersExceptioncon estos detalles poblados.

Entonces mi pregunta es: Dada una excepción de ningún escriba como entrada, proporcione un método único que generará una cadena con un formato agradable que contenga todos información relevante sobre la Excepción (por ejemplo, posiblemente llamando recursivamente getCause() entre otras cosas?) Antes de publicar, casi iba a tener una puñalada en mí mismo, pero realmente no quiero reinventar la rueda – seguramente tal cosa se debe haber hecho muchas veces antes…?

avatar de usuario flup
flipar

los java.util.logging paquete es estándar en Java SE. Su Logger incluye un método de registro sobrecargado que acepta Throwable objetos. Registrará stacktraces de excepciones y su causa para usted.

Por ejemplo:

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);

Registrará:

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)

Internamente, esto hace exactamente lo que sugiere @ philipp-wendler, por cierto. Ver el código fuente para SimpleFormatter.java. Esta es solo una interfaz de nivel superior.

  • Tenga en cuenta que si usa un formateador personalizado, debe admitir específicamente el uso de Throwables (acabo de copiar el código de Oracle) o, de lo contrario, esto no funcionará.

    – Dynomyte

    4 de septiembre de 2013 a las 0:34

  • Además de esta idea, verifiqué cómo hacerlo usando SL4J y encontré esto: baeldung.com/slf4j-log-excepciones

    – Martín Larizate

    13 de junio de 2020 a las 22:29

  • Esta respuesta fue útil. Estaba probando si podía registrar excepciones de una manera que incluyera toda la información sobre el error (incluido el mensaje de error de la excepción original profundamente anidada) y también permitiera que el programa continuara procesando más trabajo. Esto funcionó bien. La cadena registrada incluida INFO: could not complete job for user Matt, com.mattwelke.Main$MathException: failed to do mathy Caused by: java.lang.IllegalArgumentException: you tried to divide 10.0 by 0. (lo que significa que obtuve todo el contexto de error que necesitaba en los registros). Me imagino que esto también funcionaría bien con SLF4J.

    – Matt Welke

    24 de enero a las 5:36

Avatar de usuario de Harish Gokavarapu
Harish Gokavarapu

Debería ser bastante simple si está utilizando LogBack o SLF4J. lo hago como abajo

//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);
}

Avatar de usuario de Philipp Wendler
philipp wendler

¿Qué tiene de malo el printStacktrace() método proporcionado por Throwable (y por lo tanto todas las excepciones)? Muestra toda la información que solicitó, incluidos el tipo, el mensaje y el seguimiento de la pila de la excepción raíz y todas las causas (anidadas). En Java 7, incluso le muestra la información sobre las excepciones “suprimidas” que pueden ocurrir en una declaración de prueba con recursos.

Por supuesto que no querrías escribir a System.errque es lo que hace la versión sin argumentos del método, así que en su lugar use una de las sobrecargas disponibles.

En particular, si solo desea obtener una cadena:

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

Si por casualidad usas el gran Guayaba biblioteca, proporciona un método de utilidad para hacer esto: com.google.common.base.Throwables#getStackTraceAsString(Throwable).

  • Gracias por su respuesta, pero creo que todo lo que hace printStacktrace() es escribir el stacktrace devuelto por getStacktrace() en el flujo de error. Esto no es realmente lo que estoy buscando. Además, no crea que funcionaría en el caso de que la Excepción envuelva otra Excepción.

    – Steve Cámaras

    30 de enero de 2013 a las 14:50

  • Las sobrecargas de printStackStrace escribir en otro destino, no en el flujo de error. Y la información impresa contiene todas las causas, incluido su tipo, mensaje, seguimiento de pila y causas (anidadas).

    – Philipp Wendler

    30 de enero de 2013 a las 14:53

  • Simplemente pruebe mis 3 líneas de código, estoy seguro de que quedará satisfecho.

    – Philipp Wendler

    30 de enero de 2013 a las 14:53

  • No es necesario crear el suyo propio, J2SE estándar proporciona esto. Mira mi respuesta.

    – flup

    30 de enero de 2013 a las 15:12

  • @flup ¿Por qué considerarías esto “hacerlo tú mismo”? De esta manera lo hace use el método estándar de Java para esto.

    – Philipp Wendler

    30 de enero de 2013 a las 15:19

Un script de registro que escribí hace algún tiempo podría ser de ayuda, aunque no es exactamente lo que quieres. Actúa de forma similar a System.out.println pero con mucho más información sobre StackTrace, etc. También proporciona texto en el que se puede hacer clic para 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;
}

Observe que esto sale a la consola, puede cambiar eso, vea http://docs.oracle.com/javase/1.4.2/docs/api/java/util/logging/Logger.html para más información sobre eso.

Ahora, lo siguiente probablemente hará lo que quieras. Pasará por todas las causas de un Throwable y lo guardará en una Cadena. Tenga en cuenta que esto no utiliza StringBuilderpara que pueda optimizarlo cambiándolo.

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();
}

Saludos,
daniel

También puedes usar Apache ExceptionUtils.

Ejemplo:

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

}

Salida de la consola:

java.lang.NullPointerException
    at com.aimlessfist.avengers.ironman.Test.main(Test.java:11)

  • A partir de la versión 3 de las utilidades de idioma, el método ha cambiado de nombre a ExceptionUtils#getStackTrace(...)!

    – ltlBeBoy

    22 de junio a las 14:31

Avatar de usuario de Saclyr Barlonium
saclyr barlonio

Algo que hago es tener un método estático que maneja todas las excepciones y agrego el registro a un JOptionPane para mostrárselo al usuario, pero podría escribir el resultado en un archivo en FileWriter envuelto en un BufeeredWriter. Para el método estático principal, para detectar las excepciones no detectadas, hago lo siguiente:

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

Y en cuanto al método:

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

En tu caso, en lugar de “gritarle” al usuario, podrías escribir un registro como te dije antes:

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

Espero haber ayudado.

Que tengas un lindo día. 🙂

  • A partir de la versión 3 de las utilidades de idioma, el método ha cambiado de nombre a ExceptionUtils#getStackTrace(...)!

    – ltlBeBoy

    22 de junio a las 14:31

¿Ha sido útil esta solución?