Java日志框架中真的需要判断log.isDebugEnabled()吗?

很多人在使用日志框架时进行debug()输出时都会先判断一下当前的日志级别,如:

if (log.isDebugEnabled()) {
    log.debug(... ...);
}

实际上,在多数时候这是不必要的。

为什么要提前判断?

在N久以前,很多日志框架都不支持{}模板的写法(如Log4j1.X, Apache Commons Logging),于是只能通过字符串拼接来输出日志内容:

log.debug("hello, this is " + name);

这样一来,每当JVM执行到此时,不管你当前的日志级别是多少,都会执行一次字符串拼接,然后将结果做为形参传递给debug()方法,这样就带来了无用的性能损耗。这时,提前判断isDebugEnabled()可以解决此问题:

if (log.isDebugEnabled()) {
    log.debug("hello, this is " + name);
}

这样写的好处有二:

  • 当日志级别在DEBUG以下时,log.debug("hello, this is " + name)就不会执行,从而没有字符串拼接的开销。
  • JIT在运行时会优化if语句,如果isDebugEnabled()返回false, 则JIT会将整个if块全部去掉。

但是现在这实际上没有必要了。如果你使用{}语法:

log.debug("hello, this is {}", name);

JVM执行到这时不会进行字符串拼接,且日志框架会自动判断DEBUG有没有开启,这时候如果再加if判断纯粹多此一举。

用Java8 Lambda化解isDebugEnabled()

如果很不幸你用的日志框架不支持{},那么还可以通过Java8 的Lambda表达式将日志输出变成以下这种格式:

log.debug( () -> new String[] {"hello, {}", name});

这里我们的log.debug()是一个包装方法,它将debug()的实际实现委托给了真正的log对象,我们在包装方法里完成isDebugEnabled()的判断,这样在业务代码中调用时就再也不需要写if (log.isDebugEnabled())了。
LogWrapper定义如下:

/** * 封装Logger是为了添加函数式编程风格 * Created by whf on 11/29/15. */
public class LogWrapper implements Logger {
    private Logger logger;

    public LogWrapper(Logger logger) {
        this.logger = logger;
    }
/** * 支持lambda的debug方法 * @param func */
    public void debug(LogArgs func) {
        if (logger.isDebugEnabled()) {
            String[] args = func.args();
            debug(args[0], slice(args, 1));
        }
    }

    private String[] slice(String[] strs, int start) {
        final int LEN = strs.length;
        if (LEN < start + 1) {
            return null;
        }

        String[] slices = new String[LEN - 1];
        for (int ix = 0 ; ix < slices.length ; ++ix) {
            slices[ix] = strs[ix + start];
        }

        return slices;
    }

这里我们自定义了一个函数式接口来支持Lambda:

@FunctionalInterface
public interface LogArgs {
    String[] args();
}

上面的slice()方法是为了模拟数组的切片,切片的概念在Go, Python, Scala等语言中很常见,因为Java原生不支持所以只能通过方法模拟。

你可能感兴趣的:(java)