73

在 Java 中记录异常时,我遇到过几次常见问题。似乎有各种不同的类型需要处理。例如,有些包装了其他异常,有些则根本没有消息——只有一个类型。

我见过的大多数代码都使用getMessage()toString() 来记录异常。但这些方法并不总能捕获查明问题所需的所有信息——其他方法,例如getCause()有时getStackTrace()提供附加信息。

例如,我现在在 Eclipse Inspect 窗口中查看的异常是InvocationTargetException. 异常本身没有原因,没有消息,没有堆栈跟踪......但是 getCause() 的目标是InvalidUseOfMatchersException,并填充了这些详细信息。

所以我的问题是:给定任何类型的异常作为输入,请提供一个方法,该方法将输出一个格式良好的字符串,其中包含有关异常的所有getCause()相关信息(例如,可能递归调用等等?)在发布之前,我几乎自己会尝试一下,但真的不想重新发明轮子-肯定以前做过很多次这样的事情......?

请不要将我指向任何特定的日志记录或实用程序框架来执行此操作。我正在寻找代码片段而不是库,因为我无权在我正在处理的项目上添加外部依赖项,并且它实际上被记录到网页的一部分而不是文件中。如果是从这样的框架中复制代码片段(并对其进行归因),那很好:-)

4

6 回答 6

89

java.util.logging包在Java SE中是标准的。它Logger包括一个接受Throwable对象的重载日志方法。它将为您记录异常的堆栈跟踪及其原因。

例如:

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

将记录:

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)

顺便说一句,在内部,这完全符合@philipp-wendler 的建议。请参阅 的源代码SimpleFormatter.java。这只是一个更高级别的接口。

于 2013-01-30T15:07:53.500 回答
22

(以及每个异常)printStacktrace()提供的方法有什么问题?Throwable它显示了您请求的所有信息,包括根异常的类型、消息和堆栈跟踪以及所有(嵌套)原因。在 Java 7 中,它甚至会向您显示有关可能在 try-with-resources 语句中发生的“抑制”异常的信息。

当然,您不想写入System.err该方法的无参数版本所做的事情,因此请改用可用的重载之一。

特别是,如果您只想获取一个字符串:

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

如果您碰巧使用了很棒的Guava库,它提供了一个实用方法来执行此操作:com.google.common.base.Throwables#getStackTraceAsString(Throwable).

于 2013-01-30T14:40:20.590 回答
20

如果您使用 LogBack 或 SLF4J,这应该很简单。我这样做如下

//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);
}
于 2015-11-03T21:21:40.180 回答
1

我前段时间编写的日志记录脚本可能会有所帮助,尽管它不是您想要的。它的行为方式类似于 System.out.println,但包含有关 StackTrace 等的更多信息。它还为 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;
}

请注意此输出到控制台,您可以更改它,有关更多信息,请参阅http://docs.oracle.com/javase/1.4.2/docs/api/java/util/logging/Logger.html

现在,以下可能会做你想要的。它将遍历 Throwable 的所有原因并将其保存在 String 中。请注意,这不使用StringBuilder,因此您可以通过更改它来优化。

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

问候,
丹尼尔

于 2013-01-30T14:42:44.517 回答
1

您还可以使用 Apache 的ExceptionUtils。

例子:

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

}

控制台输出:

java.lang.NullPointerException
    at com.aimlessfist.avengers.ironman.Test.main(Test.java:11)
于 2015-10-05T14:47:08.243 回答
1

我要做的是拥有一个处理所有异常的静态方法,并将日志添加到 JOptionPane 以将其显示给用户,但您可以将结果写入包含FileWriterBufeeredWriter. 对于主要的静态方法,要捕获未捕获的异常,我会这样做:

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

至于方法:

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

在你的情况下,你可以像我之前告诉你的那样写一个日志,而不是向用户“尖叫”:

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

我希望我有所帮助。

祝你今天过得愉快。:)

于 2016-10-27T20:34:04.863 回答