需要进行 if(log.isDebugEnabled()) { ... } 检查吗?

56

需要进行显式的if(log.isDebugEnabled()) { ... }检查吗?

我的意思是,我看到一些帖子提到log.debug("something")在记录日志之前会隐式调用以查看是否已启用调试模式日志记录。我是否漏掉了什么或者在使用之前需要执行中间步骤?

谢谢!

log.debug("ResultSet rs is retrieved from OracleTypes");

对比

if(log.isDebugEnabled()){
     log.debug("ResultSet rs is retrieved from OracleTypes");
}

编辑: 针对此问题,我写了一篇文章: http://java.sg/whether-to-do-a-isdebugenabled-checking-before-printing-out-your-log-statement/


2
出于好奇,您没有将那篇写作发表为答案的原因是什么?在SO上,这将是最受欢迎的方式。 - Joachim Sauer
5个回答

91
抱歉,我不能执行您的请求。我只能以英文回答您的问题。
if(log.isDebugEnabled()){

这个语句只是为了提高性能而使用的。由于它在内部被日志方法调用,所以它的使用是可选的。

但现在你可能会问,既然这个检查是在内部进行的,那我为什么还要使用它呢?非常简单:如果您想记录像这样简单的内容:

log.debug("ResultSet rs is retrieved from OracleTypes");

那么你就不需要进行任何检查。如果你使用append运算符(+)组合一个字符串来记录日志,就像这样:

log.debug("[" + System.getTimeInMillis() + "] ResultSet rs is retrieved from OracleTypes");

在这种情况下,您应该检查日志是否已启用,因为即使未生成日志,字符串组合也会发生。并且我必须提醒您,使用操作符“+”连接字符串非常低效。


在提到对要调试的内容进行调试之前,已经完成了相关工作,并检查是否需要打印日志语句时,如果我更改措辞是否正确,可以加一。 - OCB
5
字符串连接内部会创建很多临时对象。如果有许多像这样的日志语句,即使禁用了日志记录,您也会失去性能。 - Filipe Palrinhas
取决于字符串 - 如果这些字符串不是动态创建的,那么它们很有可能被缓存在字符串池中。无论如何,我会像你建议的那样自己做 - 始终记录相同的字符串的可能性非常小。 - Christian
由于我们正在添加一个分支,我想知道分支误判/推测执行是否会对我们造成影响,特别是当我们添加到字符串的值与log.isDebugEnabled()类似可能被缓存(例如对象ID)时-在这种情况下,添加操作可能仍然会开始,并且获取相对廉价。 - Rick Moritz
好的回答,我只是提供我的个人意见:除非性能原因真的很关键(因此您应该问自己为什么要使用Java),我的意见是避免检查,因为它会产生许多代码噪音。通常可读性得分比一些毫秒的性能更重要。 - Fabrizio Stellato

56

我知道这篇文章很旧,但是如果有人刚刚发现它...

如果你使用SLF4J,可以通过使用消息格式化来避免调用isDebugEnabled()。

例如,可以使用以下方式代替:

Object entry = new SomeObject();
logger.debug("The entry is " + entry + ".");

使用:

Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);

只有在启用调试模式时,消息格式化才会被评估。

因此,在简单情况下,您可以避免使用isDebugEnabled()。

但是,在构建参数耗费较大的情况下,仍然需要使用isDebugEnabled()(即使使用SLF4J)。

例如:

if (logger.isDebugEnabled()) {
    logger.debug("Here is the SQL: {}", sqlWrapper.buildSQL());  // assume buildSQL() is an expensive operation
}
在这种情况下,只有在实际启用调试时,您才不想评估buildSQL()。 对于SLF4J,存在一些关于始终使用它与有选择地使用它的争议。它真正的问题在于个人偏好。您可能希望在任何地方使用它,以防止另一个开发人员在将来无意中将您的日志消息更改为更复杂/昂贵的内容。

你提出了一个非常好的例子,在优化的情况下,检查操作(isDebugEnabled)非常方便。如果您想确保调试消息不会独立于logger.debug方法的内部实现而记录,则还有其他一些事情也会浮现在我的脑海中... 我的意思是,现在...你们说这个方法在内部检查调试是否启用(Logger类的javadoc也是如此)...但是如果(不应该发生)记录器的实现者更改合同(从背后刺),即使在这种情况下,您的日志也不会记录该信息。 - Victor
我认为这个说法不正确。但是,在某些情况下,如果构建其中一个参数可能很昂贵,您仍然希望使用isDebugEnabled()(即使使用SLF4J)。那么使用“{}”就没有意义了。 - ACV
1
事实上,您是错误的。这是来自SLF4J的内容:“在评估是否记录日志时,仅当决策肯定时,记录器实现才会格式化消息并将“{}”对替换为条目的字符串值。换句话说,在禁用日志记录语句的情况下,此形式不会产生参数构造成本。” - ACV
2
@yngwietiger 你是对的!该方法仍然会被调用:logger.debug("Hello World {}", test());无论日志记录器的级别如何,都会调用test() - ACV
3
@yngwietiger 哦,等等,你描述的情况——在日志消息中调用方法不是一个常规情况。是的,这个方法会被调用,但对象的 toString() 方法不会被执行。这符合文档所说的内容:logger.debug("Hello World {}", test()); test 会被调用,但是当 test 是一个对象时,logger.debug("Hello World {}", test); 不会调用其 toString() 方法。所以,对的。 - ACV
显示剩余6条评论

8

我通过在我的代码中执行检查和不执行检查的方式来检查下面的代码。有趣的是,如果我们在代码中执行检查,对于执行一百万次的4个日志语句,需要额外花费400毫秒。我正在使用SLF4J 1.6.6版本。如果你可以承受每百万个请求多花费400毫秒的时间,那么你就不需要进行检查。

    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        if (logger.isTraceEnabled()) {
            logger.trace(request.getUserID());
            logger.trace(request.getEntitlementResource().getResourceString());
            logger.trace(request.getEntitlementResource().getActionString());
            logger.trace(request.getContextMap());
        }
    }
    long endTime = System.currentTimeMillis();
    logger.fatal("With Check Enabled : " + (endTime - startTime) + " ms");

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {

        logger.trace(request.getUserID());
        logger.trace(request.getEntitlementResource().getResourceString());
        logger.trace(request.getEntitlementResource().getActionString());
        logger.trace(request.getContextMap());

    }
    endTime = System.currentTimeMillis();
    logger.fatal("With Check Disabled : " + (endTime - startTime)  + " ms");

---输出---

*2016-01-07 10:49:11,501 错误 [:http-bio-8080-exec-3] [com.citi.cmb.entitlement.service.EntitlementServiceImpl][]-启用检查: 661 毫秒

2016-01-07 10:49:57,141 错误 [:http-bio-8080-exec-3] [com.citi.cmb.entitlement.service.EntitlementServiceImpl][]-禁用检查: 1043 毫秒


3
在第一个代码块中,isTraceEnabled 只被检查了一次,但实际上这并不是这样的情况,对于每个语句,isTraceEnabled 都将被检查。如果您可以使用此更改发布结果,那将非常好。 - Kartikey
最好使用字符串连接进行比较,因为我们不知道 request 的工作原理。 - Leon
这只是一个getter。没有其他的逻辑在里面。 - Ashraff Ali Wahab

8
最近版本的Logger已经简化了这个过程,所以差别不是很大。
最大的区别在于你不需要创建用于记录的内容 - 有时会进行大量的字符串拼接。

您是否知道从哪个版本开始,Logger 开始优化流程? - OCB
我猜问题不在于记录器如何打印,而是在于甚至在logger.debug检查是否需要打印之前,logger.debug方法内的表达式(参数)就已经被评估了。这是一些计算,无论debug是否启用。我认为日志框架无法阻止这种情况发生。@yngwietiger解释得很好。 - Raja Anbazhagan
@raja 如果你先检查一下,就不会对记录表达式进行评估。更好的方法是将一个供应商传递给一个记录方法,这允许按需延迟计算。 - Bohemian
你能推荐一种特定版本或类型的日志框架吗? - Raja Anbazhagan
@raja 不是很确定。在Java中,日志记录仍然没有明确的胜者。我倾向于使用与容器/平台捆绑的任何内容。 - Bohemian

2

这样做是为了提高性能。如果首先检查这个条件,log.debug(...语句就不会被执行。

实际上,它的功能是相同的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接