很多人在使用日志框架时进行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原生不支持所以只能通过方法模拟。