提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
背景
我们的应用系统代码里,通常会有各种不同日志级别的代码,如error、warning、info、debug等日志级别。在不同的系统运行环境中,可以设置打印不同日志级别的日志。在开发环境和测试环境,会设置打印debug级别以上的日志。而在生产环境,会设置只打印info级别以上的日志,避免打印debug级别的日志对系统性能的影响。在大促时,还可以通过日志降级,只打印warning、error级别的日志,最大限度的减少对系统性能的影响,进一步提高系统吞吐量和保障系统的稳定性。
那么当我们设置好更高级别的日志级别时,是否就能真正提高系统的性能呢。有没有什么坑需要注意的呢?最近接触了几个应用系统,发现了日志打印代码的一些坑,一起来看一下。
一、日志代码的第一个坑
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
这是某个系统其中的一段debug日志代码:
logger.debug("入参:order=" + JSON.toJSONString(order));
当我们在生产环境中,设置打印info级别以上的日志,是不是觉得涉及debug日志的代码就不会对我们的系统有性能影响。
首先代码中的logger.debug方法是肯定会执行的,但我们会想到方法内部应该加了对debug日志级别的判断,当我们的日志级别是info的,是不会去真正执行日志打印。
我们首先来看下debug方法内部调用的代码:
public void debug(final String format) {
this.logger.logIfEnabled(FQCN, Level.DEBUG, (org.apache.logging.log4j.Marker)null, format);
}
public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message) {
if (this.isEnabled(level, marker, message)) {
this.logMessage(fqcn, level, marker, message);
}
}
从代码看,debug方法内部会先判断当前日志级别是否符合设置的打印日志级别,比如说我们当前设置的打印info级别以上日志,内部会通过isEnabled方法判断,对于debug级别的日志就不会去执行打印了。从这点上来看,内部处理逻辑是没有问题的。
但是我们要注意的一点是,在logger.debug方法调用时,还是会解析传入方法的入参,也就是 "入参:order=" + JSON.toJSONString(order) 这段代码,可以看到入参会执行一个字符串变量的拼接,这会导致一定的性能损耗。
二、使用占位符提升性能
对于字符串变量的拼接这个问题,在log4j之后的版本,包括log4j2和logback中,可以使用占位符的方式来替代字符串拼接,提升性能。
使用占位符优化后的debug日志代码如下:
logger.debug("入参:order={}", JSON.toJSONString(order));
三、日志代码的第二个坑
经过占位符优化后的代码还有问题吗?
我们再看下优化后的代码,可以看到第二个入参JSON.toJSONString(order)是一个方法。程序执行logger.debug方法时,必然会调用到toJSONString()这个方法,得到这个入参的值。JSON.toJSONString(order)本身是一个JSON序列化操作,如果order对象比较大的话,是比较耗时的。我们曾经测试过一个100k字节的对象,JSON序列化的时间需要50ms以上。那如何解决这个问题呢。
四、使用logger.isDebugEnabled()方法提升性能
我们可以通过使用logger.isDebugEnabled()方法进行前置判断,从而在设置info日志级别以上时,避免对debug方法的调用,从而避免了序列化耗时操作。
先看下logger.isDebugEnabled()方法内部代码:
public boolean isDebugEnabled() {
return this.logger.isEnabled(Level.DEBUG, (org.apache.logging.log4j.Marker)null, (String)null);
}
可以看到logger.isDebugEnabled()方法只对可打印的日志级别进行了一次判断,在性能方面的影响是最小的。
使用logger.isDebugEnabled()优化后的代码:
if (logger.isDebugEnabled()) {
logger.debug("入参:order={}", JSON.toJSONString(order));
}
通过在logger.debug方法调用前,预先使用logger.isDebugEnabled()方法进行判断,可以避免logger.debug方法执行,把debug日志代码对系统性能的影响降到最低。
总结
对于info,debug,trace级别的代码,都可以使用占位符和logger.isInfoEnabled、logger.isDebugEnabled、logger.isTraceEnabled方法,降低在生产环境时,不必要的日志打印对系统性能的影响。通过这种方式的优化,这几个应用系统优化后的性能比优化前的性能平均提升20%以上。