首页 文章

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

提问于
浏览
167

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

Option 1:

logger.debug("some debug text");

但有些链接表明最好首先查看 isDebugEnabled() ,如:

Option 2:

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

所以我的问题是“ Does option 2 improve performance any way? ” .

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

  • 如果为debugEnabled变量赋值,和

  • 实际上是由logger.debug()方法调用的 .

我不认为如果我们在方法或类中编写多个 logger.debug() 语句并根据选项1调用 debug() 方法,那么与选项2相比,它是Log4J框架的开销 . 因为 isDebugEnabled() 是一个非常小的方法(就代码而言) ,它可能是内联的好选择 .

15 回答

  • 2

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

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

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

    更好的是使用更新的日志记录框架,其中日志语句采用格式规范和由 Logger 替换的参数列表 - 但仅当启用了 Logger 时"lazily," . 这是slf4j采取的方法 .

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

  • 206

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

  • 28

    当您通过连接字符串来构建日志消息时,保留使用 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);
    

    这更容易阅读 .

  • 0

    简短版本:你可以做布尔isDebugEnabled()检查 .

    原因:
    1-如果复杂的逻辑/字符串连接 . 已添加到您的调试语句中,您已经有了检查 .
    2-您不必在"complex"调试语句中有选择地包含该语句 . 所有陈述都包含在内 .
    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.
    *   */
    
  • 6

    在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);
    
  • 3

    选项2更好 .

    本身并不能提高性能 . 但它确保性能不会降低 . 这是如何做 .

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

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

    logger.debug(str1 str2 str3 str4);

    等等 .

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

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

  • -1

    正如其他人所提到的,使用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 .

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

    希望这可以帮助 .

  • 0

    就像@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());
    }
    
  • 2

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

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

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

    ej:

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

    I do:

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

    但是对于 multiple lines 的代码

    ej.

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

    I do:

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

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

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

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

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

  • 7

    从2.x开始,Apache Log4j内置了此检查,因此不再需要 isDebugEnabled() . 只需执行 debug() ,如果未启用,将禁止显示消息 .

  • 7

    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
    
  • 2

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

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

    Case2:log.debug(“one string”“two string”object.toString object2.toString)

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

相关问题