【问题标题】:How to log as much information as possible for an exception in Java?如何为 Java 中的异常记录尽可能多的信息?
【发布时间】:2013-01-14 09:41:42
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: java exception logging methods stack-trace


    【解决方案1】:

    java.util.logging packageJava 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 的建议。 请参阅source code for SimpleFormatter.java。这只是一个更高级别的接口。

    【讨论】:

    • 请记住,如果您使用自定义格式化程序,则必须专门支持使用 Throwables(我只是复制了 Oracle 代码),否则这将不起作用。
    • 除了这个想法,我检查了如何使用 SL4J 来实现它并创建了这个:baeldung.com/slf4j-log-exceptions
    【解决方案2】:

    Throwable 提供的printStacktrace() 方法有什么问题(以及每个异常)?它显示了您请求的所有信息,包括根异常的类型、消息和堆栈跟踪以及所有(嵌套)原因。在 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)

    【讨论】:

    • 感谢您的回答,但我认为 printStacktrace() 所做的只是将从 getStacktrace() 返回的堆栈跟踪写入错误流?这不是我真正想要的。也不要认为它在异常包装另一个异常的情况下会起作用。
    • printStackStrace 的重载写入另一个目标,而不是错误流。并且打印的信息包含所有原因,包括它们的类型、消息、堆栈跟踪和(嵌套)原因。
    • 试试我的3行代码,相信你会满意的。
    • 无需自己动手,标准 J2SE 提供了这一点。看我的回答。
    • @flup 为什么你会认为这是“自己动手”?这种方式确实为此使用标准 Java 方法。
    【解决方案3】:

    如果你使用 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);
    }
    

    【讨论】:

      【解决方案4】:

      我前段时间编写的日志记录脚本可能会有所帮助,尽管它并不是您想要的。它的行为方式类似于 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();
      }
      

      问候,
      丹尼尔

      【讨论】:

        【解决方案5】:

        你也可以使用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)
        

        【讨论】:

          【解决方案6】:

          我要做的是有一个静态方法来处理所有异常,并将日志添加到 JOptionPane 以将其显示给用户,但您可以将结果写入 FileWriter 中的文件,并包装在 @987654322 中@。 对于主要的静态方法,要捕获未捕获的异常,我会这样做:

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

          我希望我有所帮助。

          祝你有美好的一天。 :)

          【讨论】:

            猜你喜欢
            • 2021-04-23
            • 2022-01-02
            • 1970-01-01
            • 1970-01-01
            • 2023-04-02
            • 1970-01-01
            • 2011-07-26
            • 2012-08-07
            • 1970-01-01
            相关资源
            最近更新 更多