我們經常看到各種代碼規約都要求我們在打印日誌前先做一次日誌級別判斷,例如
對於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就可以了。