十個(gè)Java日志小技巧,編碼調(diào)試更輕松
在軟件開發(fā)和維護(hù)過(guò)程中,日志記錄是監(jiān)控和診斷問(wèn)題的重要工具。本文指導(dǎo)你如何高效地使用日志,確保你的應(yīng)用程序能夠產(chǎn)生有用、清晰且高效的日志輸出。
1.選擇合適的日志級(jí)別
在軟件開發(fā)中,我們通常使用五種日志級(jí)別:錯(cuò)誤(Error)、警告(Warn)、信息(Info)、調(diào)試(Debug)和追蹤(Trace)。選擇正確的日志級(jí)別對(duì)于監(jiān)控系統(tǒng)運(yùn)行狀態(tài)有著重要作用。以下是各級(jí)別的簡(jiǎn)要說(shuō)明:
- 錯(cuò)誤(Error):記錄嚴(yán)重錯(cuò)誤,這些錯(cuò)誤會(huì)影響業(yè)務(wù)流程,需要運(yùn)維團(tuán)隊(duì)密切關(guān)注和處理。
 - 警告(Warn):記錄一般性錯(cuò)誤,雖然對(duì)業(yè)務(wù)影響不大,但應(yīng)引起開發(fā)團(tuán)隊(duì)的注意。
 - 信息(Info):記錄關(guān)鍵信息,便于問(wèn)題排查,如方法調(diào)用的時(shí)間、輸入輸出參數(shù)等。
 - 調(diào)試(Debug):記錄關(guān)鍵邏輯的運(yùn)行時(shí)數(shù)據(jù),主要用于開發(fā)過(guò)程中的問(wèn)題調(diào)試。
 - 追蹤(Trace):提供最詳盡的信息,通常僅在日志文件中記錄,用于深入分析。
 
2.精確記錄日志參數(shù)
在日志管理中,我們追求的是精準(zhǔn)而非數(shù)量。關(guān)鍵在于記錄那些能夠幫助我們快速定位問(wèn)題的日志。具體來(lái)說(shuō):
- 輸入?yún)?shù)記錄:每當(dāng)一個(gè)方法被調(diào)用時(shí),記錄其輸入?yún)?shù)。這為我們提供了方法執(zhí)行的初始狀態(tài),是問(wèn)題排查的起點(diǎn)。
 - 輸出與返回值記錄:在方法執(zhí)行完畢后,記錄其輸出參數(shù)和返回值。這些信息對(duì)于理解方法的執(zhí)行結(jié)果至關(guān)重要。
 - 關(guān)鍵信息標(biāo)記:特別留意記錄那些關(guān)鍵信息,例如用戶ID(userId)。這些細(xì)節(jié)在后續(xù)的問(wèn)題診斷和數(shù)據(jù)分析中扮演著重要角色。
 
通過(guò)這樣的日志記錄策略,能夠確保日志的實(shí)用性和有效性,為系統(tǒng)的穩(wěn)定運(yùn)行和問(wèn)題快速解決提供有力支持。
3.選擇合適的日志格式
一個(gè)良好的日志格式是高效日志管理的基礎(chǔ),應(yīng)該包含所有必要的基本信息,以便我們能夠迅速理解日志條目的上下文。日志應(yīng)包含以下核心信息:
- 時(shí)間戳:記錄事件的精確時(shí)間,以毫秒為單位。
 - 日志級(jí)別:標(biāo)識(shí)信息的緊急程度,如錯(cuò)誤、警告或信息。
 - 線程名稱:指明哪個(gè)線程生成了日志,尤其在多線程應(yīng)用中非常有用。
 
logback 日志可以這樣配置:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n</pattern>
    </encoder>
</appender>4.條件分支的日志記錄
在代碼中遇到if...else...或switch等條件判斷時(shí),建議在每個(gè)分支的起始位置添加日志記錄。
這樣做可以幫助我們?cè)趩?wèn)題排查時(shí)快速確定程序的執(zhí)行路徑,使代碼邏輯更加透明,便于追蹤和診斷問(wèn)題。
if(user.isVip()){
  log.info("User isVip, Id:{}", user, getUserId());
} else {
  log.info("User not isVip, Id:{}", user, getUserId())
}5.日志級(jí)別的條件判斷
對(duì)于trace/debug等低級(jí)別的日志,應(yīng)先進(jìn)行日志級(jí)別的條件判斷。
User user = new User(666L, "demo");
if (log.isDebugEnabled()) {
    log.debug("userId is: {}", user.getId());
}這樣做是為了避免在日志級(jí)別較高時(shí)(如warn),執(zhí)行不必要的字符串拼接或?qū)ο蟮膖oString()方法調(diào)用,從而節(jié)省系統(tǒng)資源。如果日志級(jí)別設(shè)置較高,這些操作雖然執(zhí)行了,但日志內(nèi)容并不會(huì)被輸出,因此添加日志開關(guān)判斷是推薦的做法。
6.使用SLF4J而非直接調(diào)用日志API
在日志系統(tǒng)中,我們不推薦直接使用Log4j或Logback的API,而是應(yīng)該通過(guò)SLF4J(Simple Logging Facade for Java)這個(gè)門面模式的日志框架來(lái)操作。
SLF4J能夠統(tǒng)一不同日志框架的接口,方便維護(hù)和處理日志,同時(shí)允許我們?cè)诓恍薷拇a的情況下更換底層日志實(shí)現(xiàn),提高了靈活性和可維護(hù)性。
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Demo.class);7.建議使用參數(shù)占位符而非字符串拼接
在記錄日志時(shí),推薦使用參數(shù)占位符{}而不是使用+操作符進(jìn)行字符串拼接。
不當(dāng)示例:
logger.info("處理交易,ID:" + id + " 和符號(hào):" + symbol);這種方式在性能上存在損耗,因?yàn)槊看纹唇佣紩?huì)生成新的字符串對(duì)象。
正確用法:
logger.info("處理交易,ID:{} 和符號(hào):{}", id, symbol);使用大括號(hào){}作為參數(shù)占位符,不僅代碼更簡(jiǎn)潔,而且性能更優(yōu)。相較于字符串拼接,參數(shù)占位符避免了不必要的對(duì)象創(chuàng)建,從而提升了日志記錄的效率。
8.建議異步輸出日志
日志輸出通常涉及文件或其他輸出流的IO操作,這對(duì)性能有較高要求。采用異步方式輸出日志可以顯著提升IO性能,減少對(duì)主線程的阻塞。
一般建議: 除非有特別需求,否則推薦使用異步日志輸出。這樣做可以避免日志操作影響應(yīng)用程序的響應(yīng)時(shí)間和吞吐量。
配置示例(以logback為例): 在logback中配置異步日志輸出非常簡(jiǎn)單,只需添加AsyncAppender即可。以下是配置代碼示例:
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="ASYNC"/>
</appender>通過(guò)這種方式,可以確保日志系統(tǒng)高效運(yùn)行,同時(shí)減少對(duì)主業(yè)務(wù)流程的干擾。
9.避免使用e.printStackTrace()
在異常處理中,不推薦使用e.printStackTrace()來(lái)打印錯(cuò)誤信息。
不當(dāng)做法:
try {
  // 嘗試執(zhí)行代碼
} catch(Exception e) {
  e.printStackTrace();
}這種方式會(huì)將堆棧跟蹤日志與業(yè)務(wù)代碼日志混合,不利于異常日志的檢查和分析。
推薦做法:
try {
  // 嘗試執(zhí)行代碼
} catch(Exception e) {
  log.error("錯(cuò)誤", e);
}通過(guò)使用日志框架記錄異常,可以更清晰地管理和檢索錯(cuò)誤信息。此外,e.printStackTrace()生成的堆棧信息如果過(guò)長(zhǎng),可能會(huì)導(dǎo)致內(nèi)存溢出,影響用戶請(qǐng)求處理。因此,使用日志框架的錯(cuò)誤記錄方法更為穩(wěn)妥,能夠避免內(nèi)存問(wèn)題,同時(shí)保持日志的整潔和有序。
10.全面記錄異常信息
在處理異常時(shí),我們應(yīng)當(dāng)記錄完整的錯(cuò)誤信息,而不是僅僅記錄錯(cuò)誤摘要。
不當(dāng)做法:
- 僅記錄錯(cuò)誤級(jí)別,不包含異常詳情:
 
try {
  // 嘗試執(zhí)行代碼
} catch (Exception e) {
  LOG.error("錯(cuò)誤");
}這種做法沒(méi)有記錄具體的異常信息,導(dǎo)致無(wú)法了解具體拋出了哪種異常。
- 僅記錄異常消息,不包含堆棧信息:
 
try {
  // 嘗試執(zhí)行代碼
} catch (Exception e) {
  LOG.error("錯(cuò)誤", e.getMessage());
}這種方式只記錄了異常的基本描述,缺少詳細(xì)的堆棧信息,不利于深入分析和排查問(wèn)題。
正確做法:
try {
  // 嘗試執(zhí)行代碼
} catch (Exception e) {
  LOG.error("錯(cuò)誤", e);
}通過(guò)這種方式,可以記錄完整的異常堆棧信息,這對(duì)于后續(xù)的問(wèn)題診斷和修復(fù)非常關(guān)鍵。















 
 
 













 
 
 
 