码农的世界程序员技术栈

打印日志前真的要判断级别么

2019-01-27  本文已影响2人  真海

我们经常看到各种代码规约都要求我们在打印日志前先做一次日志级别判断,例如

对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断。

这样做的好处主要是为了性能优化,虽然一般打印日志的方法里面都会判断日志级别,但是在调用时可能会有一些字符串的拼接或者方法的调用,这样就造成了一些不必要的开销。

if (logger.isDebugEnabled()) {

    logger.debug("orderId is {}", getOrderId());  //避免多余的方法调用,尤其是复杂的计算

}

另外一个点日志打印时一般推荐的是使用占位符而不是直接的字符串拼装,原因也是同理,因为使用占位符的方式可以将字符串的拼装延迟到真正需要的时候做,避免用户没有进行日志级别判断导致日志内容组装带来的开销。不过使用占位符在性能上要比直接进行字符串拼装要稍微差一点点(因为会有一些格式解析等),而且在方法调用是会需要多余的Object[]来传递参数,因此,一般日志框架会提供两个重载的方法来替代直接使用可变参数的形式。

    public void trace(String format, Object arg);

public void trace(String format, Object arg1, Object arg2);  //避免创建Object[]来存储参数

结合上面两点,无论是提前判断级别还是使用占位符都是期望将参数的拼装延迟到真正需要需要的时候在执行,两者好像只需要其中一种就可以了。

如果让我选择,我会选择使用占位符的方式,毕竟在每一个打印日志的地方都加上日志级别判断是一件非常麻烦的事情,而且代码看起来也比较丑陋。如果要打印的日志不存在方法的调用,只是单独的字符串拼装的话,完全可以使用占位符,去掉烦人的级别判断。

那如果在拼装日志时,需要复杂的计算怎么办呢,难道只有日志级别判断的方法么?

与占位符类似,我们可以使用一个对象将日志计算封装起来,然后判断日志级别后再调用对象的计算逻辑生成真正的日志字符串,不过这种方式会生成多余的一个对象。

public class LogWrapper  {
      private Logger logger;
      public void info(LogBuilder builder) {
            if (logger.isLoggable(Level.INFO)) {
                    logger.info(builder.build());
            }
        }

      static interface LogBuilder {
          String build();
      }
}

为了避免每次打印日志的时候都使用new的方式来生成对象,我们可以使用lambda的方式(不过lambda并非只是简单的语法糖,本身调用就存在一些开销)

log.info(() -> "orderId is " + getOrderId());

这样既可以达到延迟执行日志计算,也可以去掉日志级别的判断,不过如果只是简单的字符串拼装,还是使用占位符更经济,因此在wrapper中加上占位符的方法,这样直接使用wrapper就可以了。

上一篇 下一篇

猜你喜欢

热点阅读