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

我使用< em > Log4J < / em >在我的应用程序日志。之前我使用的调试调用如下:

选项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()语句并根据选项1调用debug()方法,那么与选项2相比,Log4J框架的开销是很大的。由于isDebugEnabled()是一个非常小的方法(就代码而言),它可能是内联的好候选。

133679 次浏览

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

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

选择2更好。

它本身并不能提高性能。但它确保性能不会下降。这是如何。

通常我们期望 logger.debug (someString); < / p >

但通常,随着应用程序的发展,会有很多人转手,尤其是新手开发人员

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

诸如此类。

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

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

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

守卫语句(检查isDebugEnabled())的存在是为了防止日志消息的潜在昂贵的计算,当它涉及到调用各种对象的toString()方法并连接结果时。

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

更好的方法是使用最新的日志记录框架,其中日志语句接受格式规范和参数列表,由logger—替换,但是“惰性地”,仅在启用日志记录器的情况下。这是slf4j所采用的方法。

有关更多信息,请参阅我对一个相关问题的回答,以及使用log4j执行类似操作的示例。

使用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);

这样就更容易阅读了。

因为在选项1中,消息字符串是常量,用条件包装日志语句绝对没有好处,相反,如果日志语句是调试启用的,你将计算两次,一次在isDebugEnabled()方法中,一次在debug()方法中。调用isDebugEnabled()的代价在5到30纳秒之间,对于大多数实际目的来说可以忽略不计。因此,选项2是不可取的,因为它会污染您的代码,并且不会带来其他好处。

就像@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,您正在进行布尔检查,这是快速的。在选项一中,你做一个方法调用(把东西压到堆栈上),然后做一个布尔检查,这仍然是快速的。我看到的问题是一致性。如果你的一些调试和信息语句被包装,而另一些没有,这不是一个一致的代码风格。另外,稍后有人可以更改调试语句以包含连接字符串,这仍然非常快。我发现,当我们在一个大型应用程序中包装debug和info语句并对其进行分析时,我们在性能上节省了几个百分点。虽然不多,但也足够值得一试了。现在,我在IntelliJ中设置了两个宏来自动生成封装的调试和信息语句。

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

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

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

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

你也可以很容易地“滚动”你自己的版本。

希望这能有所帮助。

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

< p >原因:
1-如果复杂的逻辑/字符串连接。
?
2-你不必有选择地在“复杂”调试语句中包含语句。
.
. 3-在记录日志之前调用log.debug执行以下操作:

. log.debug < p > <代码>如果(repository.isDisabled (Level.DEBUG_INT))
返回; <代码> /
< / p >

这基本上与调用log相同。或猫。isDebugEnabled()。

然而!这是log4j开发人员的想法(因为它在他们的javadoc中,您可能应该这样做。)

这就是方法

public
boolean isDebugEnabled() {
if(repository.isDisabled( Level.DEBUG_INT))
return false;
return Level.DEBUG.isGreaterOrEqual(this.getEffectiveLevel());
}
这是它的javadoc
< / p >
/**
*  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.
*   */

我建议大多数人使用选项2,因为它不是特别贵。

< p >案例1: Log.debug ("one string")

< p >例2: Log.debug(“一个字符串”+“两个字符串”+对象。toString + object2.toString)

在调用这两个函数时,必须计算log.debug中的参数字符串(无论是CASE 1还是Case2)。这就是人们所说的“昂贵”。如果你在它之前有一个条件,'isDebugEnabled()',这些不需要计算,这是性能保存的地方。

对于一行,我在日志消息中使用了三元,这样我就不做串联:

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

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

由于许多人在搜索log4j2时可能会看到这个答案,而且几乎所有当前的答案都不考虑log4j2或它最近的变化,因此这应该能够回答这个问题。

log4j2支持__abc0(目前是它们自己的实现,但根据文档,它计划在3.0版使用Java的Supplier接口)。你可以在手册中阅读更多关于此的内容。这允许你把昂贵的日志消息创建到一个供应商,它只创建消息,如果它将被记录:

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

截至2。x, Apache Log4j内置了这个检查,所以isDebugEnabled()不再是必要的。只要执行debug(),如果未启用,消息将被抑制。

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

由于Log4j版本2.4(或__abc1),最好使用流利的API(或Java 8 lambda支持惰性日志记录),支持Supplier<?>作为日志消息参数,可以由λ给出:

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

OR与slf4j API:

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