在 log4j 中,在记录之前检查 isDebugEnabled 是不是会提高性能?

Posted

技术标签:

【中文标题】在 log4j 中,在记录之前检查 isDebugEnabled 是不是会提高性能?【英文标题】:In log4j, does checking isDebugEnabled before logging improve performance?在 log4j 中,在记录之前检查 isDebugEnabled 是否会提高性能? 【发布时间】:2010-11-01 02:23:37 【问题描述】:

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

    如果为 debugEnabled 变量赋值,并且 实际上是由logger.debug()方法调用的。

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

【问题讨论】:

【参考方案1】:

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

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

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

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

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

【讨论】:

log5j 扩展 log4j 的方式与 slf4j 非常相似 这也是java.util.Logging的做法。 @Geek 禁用日志事件时效率更高,因为日志级别设置为高。请参阅我的 answer here. 中标题为“条件日志记录的必要性”的部分 这在 log4j 2 中有改变吗? @SnakeDoc 不。这是方法调用的基础:方法 arg-lists 中的表达式在调用之前被有效评估。如果这些表达式 a) 被认为很昂贵并且 b) 仅在某些条件下需要(例如,当启用调试时),那么您唯一的选择就是在调用周围进行条件检查,而框架无法为您做到这一点。基于格式化程序的日志方法的问题是您可以传递一些对象(本质上是免费的),并且记录器仅在需要时才会调用toString()【参考方案2】:

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

【讨论】:

【参考方案3】:

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

这更容易阅读。

【讨论】:

您对 String.format(...) 的使用虽然使日志行更易于阅读,但实际上可能会对性能产生不良影响。这样做的 SLF4J 方法将参数发送到 logger.debug 方法,并且在构造字符串之前完成 isDebugEnabled 的评估之前。您使用 String.format(...) 执行此操作的方式将在对 logger.debug 的方法调用完成之前构造字符串,因此即使调试级别为未启用。很抱歉挑剔,只是想避免新手混淆;-) String.format 比 concat 慢 40 倍 & slf4j 有 2 个参数的限制 在此处查看数字:***.com/questions/925423/… 我见过很多分析器图,其中在生产时在调试语句中浪费了格式操作系统正在以 INFO 或 ERROR 的日志级别运行【参考方案4】:

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

【讨论】:

非常酷。但 slft4j 直到版本 2 才支持(仍处于 alpha 阶段):(【参考方案5】:

正如其他人所提到的,只有在创建字符串是一个耗时的调用时,使用 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。

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

希望这会有所帮助。

【讨论】:

【参考方案6】:

短版:你不妨做布尔 isDebugEnabled() 检查。

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

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

这与调用日志基本相同。或猫。是调试启用()。

但是!这就是 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.
*   */

【讨论】:

感谢您提供 JavaDoc。我知道我以前在某个地方看到过这个建议,并试图找到一个明确的参考。这是,如果不是确定的,至少是非常了解情况的。【参考方案7】:

选项 2 更好。

它本身并没有提高性能。但它确保性能不会降低。方法如下。

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

但通常,随着应用程序的增长,会更换很多人,尤其是新手开发人员,您可以看到

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

等等。

即使日志级别设置为 ERROR 或 FATAL,字符串的连接也会发生! 如果应用程序包含大量带有字符串连接的调试级别消息,那么它肯定会降低性能,尤其是在 jdk 1.4 或更低版本中。 (我不确定后续版本的 jdk 内部是否会执行任何 stringbuffer.append())。

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

对于单行,我在日志消息中使用三元组,这样我不进行连接:

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】:

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

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

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

【讨论】:

【参考方案11】:

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

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

【讨论】:

如果我们使用 jdk 1.5 及更高版本,那么我认为连接字符串不会有任何区别。 怎么回事? JDK5 会有什么不同? 在 jdk 1.5 中,如果我们在单个语句中连接字符串,那么在内部它只使用 StringBuffer.append() 方法。所以它不会影响性能。 字符串连接无疑需要时间。但是,我不确定我是否会将其描述为“昂贵”。上面的例子节省了多少时间?与周围的代码实际在做什么相比? (例如数据库读取或内存计算)。我认为这类陈述需要限定 即使 JDK 1.4 也不会使用简单的字符串连接创建新的 String 对象。当根本不应该显示任何字符串时,性能损失来自使用 StringBuffer.append()。【参考方案12】:

由于Log4j版本2.4(或slf4j-api 2.0.0-alpha1)最好使用fluent API(或Java 8 lambda support for lazy logging),支持Supplier&lt;?&gt;作为日志消息参数,可以给出lambda

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

或使用 slf4j API:

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

【讨论】:

“好多了”在旁观者的眼中 :-) 后者比你试图避免的更难理解而且更长。【参考方案13】:

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

【讨论】:

【参考方案14】:

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

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

案例2: log.debug("一个字符串" + "两个字符串" + object.toString + object2.toString)

在调用其中任何一个时,必须评估 log.debug 中的参数字符串(无论是 CASE 1 还是 Case2)。这就是每个人所说的“昂贵”的意思。如果您在它之前有一个条件“isDebugEnabled()”,则不必评估这些条件,这是保存性能的地方。

【讨论】:

【参考方案15】:

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

【讨论】:

【参考方案16】:

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 中,在记录之前检查 isDebugEnabled 是不是会提高性能?的主要内容,如果未能解决你的问题,请参考以下文章

如何在记录器 xml 标记内的 appender refs 中禁用 log4j2.xml 可加性

我们可以在运行时更改log4j的日志记录级别吗?

无法使用 log4j 在数据库中插入数据

在插入之前检查现有记录

在将记录添加到数据库之前检查记录是不是存在

log4j 1.2 - 异步记录器内存使用限制设置