背景
在日常開發中,項目會使用抽象日誌接口slf4j來打印日誌。如下是一段典型的打印日誌代碼:
logger.debug("hello, world");
但是在一些項目或第三方開源框架中,也會發現有些代碼在輸出日誌時,在前面添加if判斷,代碼如下:
if(logger.isDebugEnabled()){
logger.debug("hello, world");
}
簡單來說,先使用isDebufEnabled
來判斷日誌級別。這麼寫的目的是什麼呢?而且網上有些文檔指出這種判斷其實是不需要的。那實際開發中,我們到底要不要判斷isDebugEnabled
呢?希望通過這篇文章來分享一些我對日誌打印的思考。
簡單的源碼剖析
有些人不明白爲什麼要添加if判斷,會認爲這樣是爲了控制日誌的輸出。其實這是不對的。對於下面的兩段代碼:
logger.debug("hello, world")
if(logger.isDebugEnabled()){
logger.debug("hello, world");
}
如果應用的日誌級別大於debug
,比如爲info
。那麼這兩段代碼,最終都不會輸出日誌。在debug
方法內部,會判斷日誌級別,如果應用級別大於日誌級別,就不會輸出日誌。以下是isDebugEnabled
和debug
的核心代碼(我刪除了一些無關代碼):
isDebugEnabled
方法:
public boolean isDebugEnabled(Marker marker) {
final FilterReply decision = callTurboFilters(marker, Level.DEBUG);
if (decision == FilterReply.NEUTRAL) {
return effectiveLevelInt <= Level.DEBUG_INT;
} else {
throw new IllegalStateException("Unknown FilterReply value: " + decision);
}
}
debug
方法:
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);
if (decision == FilterReply.NEUTRAL) {
if (effectiveLevelInt > level.levelInt) {
return;
}
} else if (decision == FilterReply.DENY) {
return;
}
buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}
那麼問題來了,既然isDebugEnabled
不會影響日誌是否輸出,那爲什麼要添加這個判斷呢?
爲什麼要添加if判斷
我們考慮下面幾段代碼:
代碼清單一:
logger.debug("hello, world")
代碼清單二:
if(logger.isDebugEnabled()){
logger.debug("hello, world")
}
代碼清單三:
logger.debug("hello" + "world");
代碼清單四:
logger.debug(String.format("hello, %s", "world"))
代碼清單五:
logger.debug("hello {}", "world");
以上5段代碼,如果應用日誌級別爲info,那麼這5段代碼都不會輸出日誌。它們的差異其實是性能不同。下面我們來逐個分析。
- 代碼清單一:執行
debug
方法。debug
方法內部判斷日誌級別,然後退出。 - 代碼清單二:
isDebugEnabled
判斷日誌級別,然後退出。 - 代碼清單三:先拼接字符串“hello”和“world”。然後執行
debug
方法。 - 代碼清單四:先執行
String.format
方法,然後執行debug
方法。 - 代碼清單五:執行
debug
方法。
大家發現了嗎?雖然最終都不會輸出日誌,但這5段代碼還是有差異的。代碼清單三和代碼清單四分別執行了"+"字符串拼接和String.format
方法,但最終卻沒用到。也就是說,這兩段代碼執行了一些無用操作,造成了額外的性能損耗。
所以,代碼清單二中添加isDebugEnabled
可以避免無用的字符串操作,提高性能。
isDebugEnabled是必需的嗎?
上一節中,我提到使用isDebugEnabled
可以提升性能,那是不是在所有地方都需要添加isDebugEnabled
判斷呢?
先說結論,不是的。應該根據具體場景來判斷是否添加isDebugEnabled
。
比如下面的代碼:
logger.debug("hello, world");
打印一條日誌“hello,world”。那麼這時候就不用添加isDebugEnabled
判斷了。關於isDebugEnabled
的使用場景,總結了如下的最佳實踐:
isDebugEnabled最佳實踐
原則一:如果打印字符串常量,不需要isDebugEnabled
比較下面兩段代碼:
logger.debug("hello, world");
if(logger.isDebugEnabled()){
logger.debug("hello, world");
}
因爲打印的日誌是字面常量,沒有計算邏輯。兩段代碼的性能是幾乎一樣的。添加isDebugEnabled
反而會導致額外的代碼。
原則二:如果有參數,且參數只是字符串常量或計算簡單,使用佔位符
考慮如下代碼,debug
方法包含了參數user.getName()
。雖然執行debug
方法時,會計算user.getName()
,但只是一個簡單的get方法,沒有複雜計算,這時候,也可以不添加isDebugEnabled
。
logger.debug("hello, {}", user.getName());
原則三:如果有參數,且參數計算複雜,添加isDebugEnabled
logger.debug("order price: {}", calculatePrice());
假設calculatePrice
方法需要經過複雜計算。那麼就應該添加isDebugEnabled
判斷,使用如下的代碼:
if(logger.isDebugEnabled()){
logger.debug("order price: {}", calculatePrice());
}