在log4j中,在记录之前检查isDebugEnabled是否可以提高性能?


207

我在应用程序中使用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()方法调用的。

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

Answers:


247

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

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

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

更好的方法是使用最新的日志记录框架,在该框架中,日志语句采用格式规范和由记录器替换的参数列表,但只有在启用记录器后才“懒惰”。这是slf4j采取的方法。

有关更多信息,请参见我对相关问题的回答,以及使用log4j进行此类操作的示例。


3
log5j以与slf4j几乎相同的方式扩展log4j
Bill Michell

这也是java.util.Logging的方法。
保罗

@Geek禁用日志事件会更有效,因为日志级别设置为高。请参阅我的答案中
erickson

1
这在log4j 2中有变化吗?
SnakeDoc '16

3
@SnakeDoc否。这是方法调用的基础:方法arg-lists中的表达式在调用之前会得到有效评估。如果这些表达式是a)被认为很昂贵并且b)仅在特定条件下才需要的表达式(例如,当启用调试时),那么您唯一的选择是对调用进行条件检查,而框架无法为您做到这一点。基于格式化程序的日志方法的作用是,您可以传递一些对象(基本上是免费的),并且记录器toString()仅在需要时调用。
SusanW '17

31

由于在选项1中消息字符串是一个常量,因此用条件包装换行记录语句绝对没有好处,相反,如果启用了调试语句,则将进行两次评估,一次在isDebugEnabled()方法中,一次在系统中。debug()方法。调用成本isDebugEnabled()大约为5到30纳秒,对于大多数实际目的而言应该可以忽略不计。因此,选项2是不理想的,因为它会污染您的代码,并且不会带来其他收益。


17

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

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

但是,在您的示例中,仅记录一个String而不执行诸如串联之类的操作就不会提高速度。因此,您只是在代码中添加了膨胀,使其难以阅读。

我亲自在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);

甚至更容易阅读。


8
您使用String.format(...)会使日志行更易于阅读,但实际上可能会对性能产生不良影响。SLF4J的执行方式将参数发送到logger.debug方法中,并且在构造字符串之前对isDebugEnabled进行了评估。使用String.format(...)的方式将在对logger.debug的方法调用完成之前构建字符串,因此即使调试级别为未启用。对不起,我只是为了避免让新手感到困惑;-)
StFS 2015年

2
String.format比concat慢40倍,并且slf4j的2个参数有限制。请参见此处的数字: stackoverflow.com/questions/925423/… 我已经看到了许多探查器图,其中在生产系统处于运行状态时,格式操作在调试语句中被浪费了以INFO或ERROR日志级别运行
AztecWarrior_25 '18


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.
*   */

1
感谢您加入JavaDoc。我知道我以前曾在某处看到过此建议,并试图找到明确的参考。如果不是确定的,这至少是非常有根据的。
西蒙·彼得·查佩尔

7

正如其他人提到的那样,只有在创建字符串是耗时的调用时,使用guard语句才真正有用。这方面的具体示例是在创建字符串时将触发一些延迟加载。

值得注意的是,这个问题就可以完成避免使用简单的日志门面为Java或(SLF4J) - http://www.slf4j.org/manual.html。这允许方法调用,例如:

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

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

您还可以非常轻松地“滚动自己的”版本。

希望这可以帮助。


6

选项2更好。

本身并不能提高性能。但这可以确保性能不会降低。这是如何做。

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

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

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

等等。

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

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


3

就像@erickson一样,这取决于。如果我记得,isDebugEnabled已经在debug()Log4j 的方法中构建了。
只要您不在调试语句中进行一些昂贵的计算,例如在对象上循环,执行计算和连接字符串,就我而言就可以了。

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

3

对于单行,我在日志记录消息中使用三元组,这样就不会进行串联:

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

3

由于许多人在搜索log4j2时可能正在查看此答案,并且几乎所有当前答案都未考虑log4j2或最近的更改,因此希望可以回答该问题。

log4j2支持Supplier(目前是他们自己的实现,但是根据文档,计划使用3.0版的Java的Supplier接口)。您可以在手册中阅读更多有关此内容的信息。这使您可以将昂贵的日志消息创建工作放到供应商中,后者仅在要记录消息时才创建消息:

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

2

它提高了速度,因为通常在调试文本中串联字符串,这很昂贵,例如:

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

1
如果我们使用的是jdk 1.5及更高版本,那么我认为连接字符串不会有任何区别。
沉默的战士

怎么会?JDK5有什么不同之处?
javashlook

1
在jdk 1.5中,如果我们在单个语句中连接字符串,则在内部它仅使用StringBuffer.append()方法。因此,它不会影响性能。
沉默的战士

2
字符串连接无疑需要时间。但是我不确定我会形容它为“昂贵”。上面的示例中节省了多少时间?与周围的代码实际做什么相比?(例如,数据库读取或内存计算)。我觉得这些类型的报表必须是合格的
布莱恩·阿格纽

1
甚至JDK 1.4也不会使用简单的字符串连接来创建新的String对象。当根本不显示任何字符串时,使用StringBuffer.append()会降低性能。
javashlook

1

Log4j版本2.4(或slf4j-api 2.0.0-alpha1)开始,最好使用fluent API(或Java 8 lambda对惰性日志记录的支持),它支持Supplier<?>log message参数,可以由lambda给出:

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

或使用slf4j API:

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

0

如果使用选项2,则将执行布尔检查,这很快。在选项一中,您要进行方法调用(将内容推入堆栈),然后进行布尔检查,该检查仍然很快速。我看到的问题是一致性。如果您的某些调试和信息语句已包装而有些未包装,则这不是一致的代码样式。另外,稍后有人可以更改调试语句以包含连接字符串,这仍然相当快。我发现,当我们在大型应用程序中包装debug和info语句并对其进行概要分析时,我们节省了几个百分点的性能。数量不多,但足以使它值得工作。我现在在IntelliJ中有几个宏设置,可以为我自动生成包装的调试和信息语句。


0

我建议大多数情况下使用Option 2,因为它并不昂贵。

情况1:log.debug(“一个字符串”)

情况2:log.debug(“一个字符串” +“两个字符串” + object.toString + object2.toString)

在调用这两种方法时,必须对log.debug(是CASE 1或Case2)中的参数字符串进行评估。这就是每个人“昂贵”的意思。如果您之前有一个条件“ isDebugEnabled()”,则无需评估这些条件即可保存性能。


0

从2.x开始,Apache Log4j内置了此检查功能,因此isDebugEnabled()不再需要此检查。只需执行a即可debug(),如果未启用,则消息将被取消显示。


-1

Log4j2允许您将参数格式化为类似于的消息模板String.format(),从而消除了do的需要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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.