我在我的应用程序中使用log4j,只是想知道是否将(cc)对象传递到记录器中,如(1)中那样,这会比通过Exception传递时消耗更多的资源,例如( 2)?换句话说,(2)会比(1)便宜吗?以下是我在log4j上的示例代码。

private static Logger logger = Logger.getLogger(...);

try {
   ...
}
catch( Exception e ) {
   logger.error("blah blah blah", e);  // (1)

   logger.error("blah blah blah", e.fillInStackTrace());  // (2)
}


我在这里有点困惑,请帮忙澄清一下?

最佳答案

好吧,如果您查看fillInStackTrace,您会注意到该方法是synchronized。在Java(或任何语言)中,同步被认为是非常昂贵的操作,因为它需要获取和释放监视器。

另外,您应注意,从Throwable返回的fillInStackTrace仅记录有关在调用fillInStackTrace时调用堆栈的堆栈帧的信息。此信息直接从JVM收集,然后转换为对象表示形式,这又会浪费资源。 (最终,fillInStackTrace委托给native方法,该方法扫描调用线程的当前调用堆栈。)

但是,您应该主要考虑要在日志中显示的内容,然后根据该条件进行决定。当您显式调用fillInStackTrace时,当前调用堆栈将出现在日志中。这意味着您将不再看到进入try块的更深层次的调用堆栈(考虑您的示例)。

毕竟,除非明确需要(2)所获得的信息,否则应始终使用变量(1)。我无法自发地想到标准日志记录范围内的一个解决方案(2)有意义的示例。但是,您可以利用(2),在编写某种框架时要减少堆栈跟踪中的噪音,并且要清除与该异常无关的一些顶部堆栈条目:例如,您可能使用某种ExceptionFactory为您创建抛出的异常。然后,您不希望堆栈跟踪显示这些工厂方法的堆栈框架,因为它们与错误无关,并且会使尝试调试的用户感到困惑。因此,从工厂收到异常后,可以通过调用fillInStackTrace手动填充堆栈跟踪。

请看此示例,直观了解两个调用的区别:

class Example {
  public static void main(String[] args) {
    try {
       outerFunction();
    }
    catch (Throwable e) {
       System.err.println("Outside:");
       e.printStackTrace();
    }
  }
  static void outerFunction() throws Throwable {
    try {
       innerFunction();
    }
    catch(Exception e) {
       System.err.println("Inside:");
       e.printStackTrace();
       throw e.fillInStackTrace();
    }
  }
  static void innerFunction() {
     throw new RuntimeException("A custom exception");
  }
}


将调用这两个不同的堆栈跟踪:

Inside:
java.lang.RuntimeException: A custom exception
at Example.innerFunction(Example.java:21)
at Example.outerFunction(Example.java:13)
at Example.main(Example.java:19)
Outside:
java.lang.RuntimeException: A custom exception
at Example.outerFunction(Example.java:13)
at Example.main(Example.java:19)

09-26 18:39