Java日誌框架中需要判斷log.isDebugEnabled()嗎?

背景

在日常開發中,項目會使用抽象日誌接口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方法內部,會判斷日誌級別,如果應用級別大於日誌級別,就不會輸出日誌。以下是isDebugEnableddebug的核心代碼(我刪除了一些無關代碼):

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());
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章