Java日志中isXXXEnabled()

108 阅读4分钟

背景

在日常开发中,项目会使用抽象日志接口slf4j来打印日志。如下是一段典型的打印日志代码:

logger.debug("hello, world");

但是在一些项目或第三方开源框架中,也会发现有些代码在输出日志时,在前面添加if判断,代码如下:

if(logger.isDebugEnabled()){ logger.debug("hello, world"); }

简单来说,先使用isDebufEnabled来判断日志级别。这么写的目的是什么呢?而且网上有些文档指出这种判断其实是不需要的。那实际开发中,我们到底要不要判断isDebugEnabled呢?希望通过这篇文章来分享一些我对日志打印的思考。

有些人不明白为什么要添加if判断,会认为这样是为了控制日志的输出。其实这是不对的。对于下面的两段代码:

logger.debug("hello, world")

if(logger.isDebugEnabled()){
    logger.debug("hello, world");
}

如果应用的日志级别大于debug,比如为info。那么这两段代码,最终都不会输出日志。在debug方法内部,会判断日志级别,如果应用级别大于日志级别,就不会输出日志。

那么问题来了,既然isDebugEnabled不会影响日志是否输出,那为什么要添加这个判断呢?

为什么要添加if判断

我们考虑下面几段代码:

代码1

logger.debug("hello, world")

代码2

if(logger.isDebugEnabled()){ logger.debug("hello, world") }

代码3:

logger.debug("hello" + "world");

代码4

logger.debug(String.format("hello, %s", "world"))

代码5

logger.debug("hello {}", "world");

以上5段代码,如果应用日志级别为info,那么这5段代码都不会输出日志。它们的差异其实是性能不同。下面我们来逐个分析。

  • 代码清单一:执行debug方法。debug方法内部判断日志级别,然后退出。
  • 代码清单二:isDebugEnabled判断日志级别,然后退出。
  • 代码清单三:先拼接字符串“hello”和“world”。然后执行debug方法。
  • 代码清单四:先执行String.format方法,然后执行debug方法。
  • 代码清单五:执行debug方法。

大家发现了吗?虽然最终都不会输出日志,但这5段代码还是有差异的。代码清单三和代码清单四分别执行了"+"字符串拼接和String.format方法,但最终却没用到。也就是说,这两段代码执行了一些无用操作,造成了额外的性能损耗。曾经就出现过线上环境有个接口请求非常频繁,且LOGGER.info内的东西是针对比较大的对象进行序列化,最终导致服务器内存溢出

所以,代码清单二中添加isDebugEnabled可以避免无用的字符串操作,提高性能。

isDebugEnabled是必需的吗?

上一节中,我提到使用isDebugEnabled可以提升性能,那是不是在所有地方都需要添加isDebugEnabled判断呢?

先说结论,不是的。应该根据具体场景来判断是否添加isDebugEnabled。

比如下面的代码:

logger.debug("hello, world");

打印一条日志“hello,world”。那么这时候就不用添加isDebugEnabled判断了。

另一种打印日志的方式:使用{}占位符来打印日志,如下:

LOGGER.debug("Search parameters: {}", searchParams);

通过占位符来打印日志有个好处,那就是不需要自己去预先判断日志的级别,其底层已经帮我们去实现这个步骤了。此外,使用占位符来打印日志,对于需要拼接大量变量的场景时,该方式也会更加地直观与优雅。如下:

LOGGER.debug("Current item id is {}, size is {}, color is {}, pattern is {}.", id, size, color, pattern);

注意:在Log4j2中,这种占位符打印的方式,最多只能支持到9个变量参数。

除了Log4j2,其它的日志框架同样支持占位符的写法,比如logback等。

关于isDebugEnabled的使用场景,总结了如下的最佳实践:

原则一:如果打印字符串常量,不需要isDebugEnabled

比较下面两段代码:

logger.debug("hello, world"); if(logger.isDebugEnabled()){ logger.debug("hello, world"); }

因为打印的日志是字面常量没有计算逻辑。两段代码的性能是几乎一样的。添加isDebugEnabled反而会导致额外的代码。

原则二:如果有参数,且参数只是字符串常量或计算简单,使用占位符

考虑如下代码,debug方法包含了参数user.getName()。虽然执行debug方法时,会计算user.getName(),但只是一个简单的get方法,没有复杂计算,这时候,也可以不添加isDebugEnabled。

logger.debug("hello, {}", user.getName());

原则三:如果有参数,且参数计算复杂,添加isDebugEnabled

logger.debug("order price: {}", calculatePrice());

假设calculatePrice方法需要经过复杂计算。那么就应该添加isDebugEnabled判断,使用如下的代码:

if(logger.isDebugEnabled()){ logger.debug("order price: {}", calculatePrice()); }