在log4j中,在记录改进性能之前检查isDebugEnabled吗?

时间:2009-06-08 05:13:55

标签: java logging log4j

我在我的应用程序中使用 Log4J 进行日志记录。以前我使用调试调用如:

选项1:

logger.debug("some debug text");

但有些链接建议最好首先检查isDebugEnabled(),例如:

选项2:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text");
}

所以我的问题是“选项2能否以任何方式改善效果?”。

因为在任何情况下Log4J框架都对debugEnabled进行相同的检查。对于选项2,如果我们在单个方法或类中使用多个调试语句可能是有益的,其中框架不需要多次调用isDebugEnabled()方法(在每次调用时);在这种情况下,它仅调用isDebugEnabled()方法一次,如果将Log4J配置为调试级别,则实际上它会调用isDebugEnabled()方法两次:

  1. 如果为debugEnabled变量赋值,
  2. 实际上是由logger.debug()方法调用的。
  3. 我不认为如果我们在方法或类中编写多个logger.debug()语句并根据选项1调用debug()方法,那么与选项2相比,它是Log4J框架的开销。 {1}}是一种非常小的方法(就代码而言),它可能是内联的良好候选者。

16 个答案:

答案 0 :(得分:217)

在这种特殊情况下,选项1更好。

当涉及调用各种对象的isDebugEnabled()方法并连接结果时,保护语句(检查toString())可以防止日志消息的潜在昂贵计算。

在给定的示例中,日志消息是一个常量字符串,因此让记录器丢弃它与检查记录器是否已启用一样高效,并且由于分支较少,因此降低了代码的复杂性。

更好的是使用更新的日志记录框架,其中日志语句采用格式规范和由记录器替换的参数列表 - 但“懒惰”,仅当记录器已启用时。这是slf4j采取的方法。

有关更多信息,请参阅my answer to a related question,以及使用log4j执行此类操作的示例。

答案 1 :(得分:30)

由于在选项1中消息字符串是常量,因此使用条件包装日志记录语句绝对没有任何好处,相反,如果日志语句启用了调试,则您将在{ {1}}方法和isDebugEnabled()方法中的一次。调用debug()的成本约为5到30纳秒,对于大多数实际目的而言,这应该可以忽略不计。因此,选项2是不可取的,因为它会污染您的代码并且不会产生任何其他收益。

答案 2 :(得分:17)

通过连接字符串来构建日志消息时,保留使用isDebugEnabled()

Var myVar = new MyVar();
log.debug("My var is " + myVar + ", value:" + myVar.someCall());

但是,在您的示例中,没有速度增益,因为您只是记录字符串而不执行连接等操作。因此,您只是在代码中添加膨胀并使其更难阅读。

我个人在String类中使用Java 1.5格式调用,如下所示:

Var myVar = new MyVar();
log.debug(String.format("My var is '%s', value: '%s'", myVar, myVar.someCall()));

我怀疑有很多优化,但它更容易阅读。

请注意,尽管大多数日志记录API都提供了开箱即用的格式:例如,slf4j提供了以下内容:

logger.debug("My var is {}", myVar);

更容易阅读。

答案 3 :(得分:8)

简短版本:您也可以执行布尔值isDebugEnabled()检查。

原因:
1-如果复杂的逻辑/字符串连接。已添加到您的调试语句中,您已经进行了检查 2-您不必有选择地在“复杂”调试语句中包含该语句。所有陈述都包括在内。
3-在记录之前调用log.debug执行以下操作:

if(repository.isDisabled(Level.DEBUG_INT))
return;

这与调用日志基本相同。还是猫isDebugEnabled()。

无论其!这就是log4j开发人员的想法(就像它们在javadoc中一样,你应该可以使用它。)

这是方法

public
  boolean isDebugEnabled() {
     if(repository.isDisabled( Level.DEBUG_INT))
      return false;
    return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
  }

这是它的javadoc

/**
*  Check whether this category is enabled for the <code>DEBUG</code>
*  Level.
*
*  <p> This function is intended to lessen the computational cost of
*  disabled log debug statements.
*
*  <p> For some <code>cat</code> Category object, when you write,
*  <pre>
*      cat.debug("This is entry number: " + i );
*  </pre>
*
*  <p>You incur the cost constructing the message, concatenatiion in
*  this case, regardless of whether the message is logged or not.
*
*  <p>If you are worried about speed, then you should write
*  <pre>
*    if(cat.isDebugEnabled()) {
*      cat.debug("This is entry number: " + i );
*    }
*  </pre>
*
*  <p>This way you will not incur the cost of parameter
*  construction if debugging is disabled for <code>cat</code>. On
*  the other hand, if the <code>cat</code> is debug enabled, you
*  will incur the cost of evaluating whether the category is debug
*  enabled twice. Once in <code>isDebugEnabled</code> and once in
*  the <code>debug</code>.  This is an insignificant overhead
*  since evaluating a category takes about 1%% of the time it
*  takes to actually log.
*
*  @return boolean - <code>true</code> if this category is debug
*  enabled, <code>false</code> otherwise.
*   */

答案 4 :(得分:7)

在Java 8中,您不必使用isDebugEnabled()来提高性能。

https://logging.apache.org/log4j/2.0/manual/api.html#Java_8_lambda_support_for_lazy_logging

import java.util.logging.Logger;
...
Logger.getLogger("hello").info(() -> "Hello " + name);

答案 5 :(得分:6)

选项2更好。

本身并没有提高性能。但它确保性能不会降低。这是怎么回事。

通常我们期待 logger.debug(someString);

但通常,随着应用程序的增长,改变很多人,特别是新手开发人员,你可以看到

logger.debug(str1 + str2 + str3 + str4);

之类的。

即使将日志级别设置为ERROR或FATAL,也会发生字符串串联! 如果应用程序包含大量带有字符串连接的DEBUG级别消息,那么它肯定会受到性能影响,尤其是jdk 1.4或更低版本。 (我不确定jdk internall的更高版本是否会执行任何stringbuffer.append())。

这就是为什么选项2是安全的。即使字符串连接也不会发生。

答案 6 :(得分:6)

正如其他人所提到的,使用guard语句只有在创建字符串是一个耗时的调用时才真正有用。具体的例子是创建字符串时会触发一些延迟加载。

值得注意的是,使用Simple Logging Facade for Java或(SLF4J) - http://www.slf4j.org/manual.html可以避免此问题。这允许方法调用,例如:

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

如果启用了调试,这只会将传入的参数转换为字符串。 SLF4J顾名思义只是一个外观,日志调用可以传递给log4j。

您也可以非常轻松地“推出自己的”版本。

希望这有帮助。

答案 7 :(得分:3)

喜欢@erickson,这取决于。如果我记得,isDebugEnabled已经构建在Log4j的debug()方法中 只要你没有在你的调试语句中做一些昂贵的计算,比如循环对象,执行计算和连接字符串,我认为你很好。

StringBuilder buffer = new StringBuilder();
for(Object o : myHugeCollection){
  buffer.append(o.getName()).append(":");
  buffer.append(o.getResultFromExpensiveComputation()).append(",");
}
log.debug(buffer.toString());

会更好

if (log.isDebugEnabled(){
  StringBuilder buffer = new StringBuilder();
  for(Object o : myHugeCollection){
    buffer.append(o.getName()).append(":");
    buffer.append(o.getResultFromExpensiveComputation()).append(",");
  }
  log.debug(buffer.toString());
}

答案 8 :(得分:2)

它提高了速度,因为在调试文本中连接字符串是很常见的,例如:

boolean debugEnabled = logger.isDebugEnabled();
if (debugEnabled) {
    logger.debug("some debug text" + someState);
}

答案 9 :(得分:2)

对于单行,我在记录消息中使用了三元组,这样我就不会进行连接:

<强> EJ:

logger.debug(str1 + str2 + str3 + str4);

我这样做:

logger.debug(logger.isDebugEnable()?str1 + str2 + str3 + str4:null);

多行代码

<强> EJ。

for(Message mess:list) {
    logger.debug("mess:" + mess.getText());
}

我这样做:

if(logger.isDebugEnable()) {
    for(Message mess:list) {
         logger.debug("mess:" + mess.getText());
    }
}

答案 10 :(得分:2)

由于许多人在搜索log4j2时可能会查看此答案,并且几乎所有当前答案都不考虑log4j2或其中的最近更改,因此应该可以回答这个问题。

log4j2支持Supplier(目前是他们自己的实现,但根据文档计划在3.0版中使用Java的Supplier接口)。您可以在manual中阅读更多相关内容。这允许您将昂贵的日志消息创建放入供应商,该供应商仅在将要记录的情况下创建消息:

LogManager.getLogger().debug(() -> createExpensiveLogMessage());

答案 11 :(得分:1)

由于 Log4j 版本2.4(或slf4j-api 2.0.0-alpha1),最好使用fluent API(或Java 8 lambda support for lazy logging)来支持{{1} }作为日志消息参数,可以由 lambda 给出:

Supplier<?>

使用slf4j API进行操作:

log.debug("Debug message with expensive data : {}", 
           () -> doExpensiveCalculation());

答案 12 :(得分:0)

如果你使用选项2,你正在做一个快速的布尔检查。在选项一中,您正在进行方法调用(在堆栈上推送内容),然后执行仍然很快的布尔检查。我看到的问题是一致性。如果你的一些debug和info语句被包装而有些不是,那么它不是一致的代码风格。后来有人可以更改调试语句以包含连接字符串,这仍然非常快。我发现当我们在大型应用程序中包装debug和info语句并对其进行分析时,我们在性能上节省了几个百分点。不多,但足以让它值得工作。我现在在IntelliJ中设置了几个宏来自动为我生成包装的调试和信息语句。

答案 13 :(得分:0)

从2.x开始,Apache Log4j内置了此检查,因此不再需要SELECT page_id, ad_id, count() cnt FROM TABLE_NAME GROUP BY page_id, ad_id ORDER BY page_id, cnt DESC LIMIT 1 BY page_id 。只需执行isDebugEnabled(),如果未启用,则会禁止显示消息。

答案 14 :(得分:-1)

我建议大多数人使用选项2作为事实,因为它不是非常昂贵。

案例1: log.debug(“一个字符串”)

情况2: log.debug(“one string”+“two string”+ object.toString + object2.toString)

在调用其中任何一个时,log.debug中的参数字符串(可以是CASE 1或Case2)都要进行评估。这就是每个人都意味着'昂贵'。如果您之前有条件'isDebugEnabled()',则不必评估这些是保存性能的位置。

答案 15 :(得分:-1)

Log4j2允许您将参数格式化为消息模板,类似于String.format(),因此无需执行isDebugEnabled()

Logger log = LogManager.getFormatterLogger(getClass());
log.debug("Some message [myField=%s]", myField);

示例简单log4j2.properties:

filter.threshold.type = ThresholdFilter
filter.threshold.level = debug
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d %-5p: %c - %m%n
appender.console.filter.threshold.type = ThresholdFilter
appender.console.filter.threshold.level = debug
rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT