1.選擇恰當的日志級別
日常開發中常見日志級別有:trace、debug、info、warn、error(級別依次增大):
1.trace:最詳細的日志信息,一般記錄到日志文件中
2.debug:一般用於開發中DEBUG的關鍵邏輯的運行時數據
3.info:記錄排查問題的關鍵信息,如出參,入參等
4.warn:警告日志,一般的錯誤,對正常業務影響不大,需要開發者關注
5.error:錯誤日志,對正常業務有影響,需要運維配置日志監控
2.日志要打印方法的入參和出參
例如在Controller層,請求入參、響應出參和響應異常,一般需要打印日志,出問題時,方便追蹤代碼邏輯運行的路線。建議這里使用日志切面進行統一日志打印;
其他層級的方法入參和出參,如有必要,可以打印整個出參和入參的數據,反之可以打印有效的關鍵日志,方便問題定位即可;
3.日志格式
一般日志中包含:時間、日志級別、線程名稱、日志具體內容等
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger:%line - %msg%n
4.在多個if-else等條件時,每個分支首行盡量打印日志
可以在進入分支前打印日志,后續可以快速定位到進入了哪個分支,方便排查問題
String requestNo = "RN7195458555001"; String channel = "weixin"; log.info("請求流水號[{}]支付處理,渠道為:{}", requestNo, channel); if (Objects.equals("zhifubao", channel)) { // TODO } else if (Objects.equals("yinlian", channel)) { // TODO } else if (Objects.equals("weixin", channel)) { // TODO } else { // TODO }
5.日志級別比較低時,進行日志開關判斷
對於trace、debug級別的日志打印,需要進行開關判斷
if (log.isTraceEnabled()) { log.trace("trace log detail......"); } if (log.isDebugEnabled()) { log.debug("debug log detail......"); }
比如需要打印如下日志
log.debug("Payment processing,requestNo=" + requestNo + ",channel=" + channel);
當前配置的日志級別為info,則上面日志不會打印,但會進行字符串拼接;如果是對象,還會執行toString()方法,浪費了系統資源
6.不要直接使用日志系統(Log4j、Logback)中的 API,而是使用日志框架SLF4J中的API
SLF4J是門面模式的日志框架,需要更換日志框架實現時,在不改動代碼的情況下,可以方便切換到不同的日志實現框架
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Demo.class);
// 或者使用Lombok @Slf4j
7.建議使用參數占位{},而不是用+拼接
反例:
log.debug("Payment processing,requestNo=" + requestNo + ",channel=" + channel);
字符串使用"+"進行拼接操作,會有一定的性能損耗,雖然高版本的jdk對字符串拼接進行了性能優化,但不建議使用
正例:
log.info("Payment processing,requestNo={},channel={}", requestNo, channel);
使用大括號{}進行占位符的替換,相比字符串拼接,性能上更高,日志代碼也更加優雅
8.不要使用e.printStackTrace() 和 System.out.println();
反例:
try{ // TODO 業務代碼處理 }catch(Exception e){
//System.out.println("xxx業務處理異常,異常信息:" + e); e.printStackTrace(); }
正例:
try { // TODO 業務代碼處理 } catch (Exception e) { log.error("xxx業務處理異常 requestNo={}", requestNo, e); }
e.printStackTrace()打印在堆棧信息中,如果異常過多,會導致堆棧內存不足,出現卡(運行極慢)的現象,最后出現OOM,這是一種非常糟糕的現象;
使用log來打印日志會記錄到日志文件中,占用的是磁盤內存,一般不會經常出現卡(運行極慢)的現象,但如果磁盤內存占用比較高時,需要對日志進行備份處理,然后清理日志;
9.異常日志不要只打一半,要輸出全部錯誤信息
反例:
log.error("xxx業務處理異常");
log.error("xxx業務處理異常", e.getMessage());
e.getMessage()
不會記錄詳細的堆棧異常信息,只會記錄錯誤基本描述信息,不利於排查問題。
正例:
log.error("xxx業務處理異常 requestNo={}", requestNo, e);
10.禁止在線上環境開啟 debug
一般業務系統的debug日志較多,引入的第三方框架debug日志也較多,隨着業務交易的增多,容易占用磁盤內存空間,最后可能會影響正常業務系統的運行
,所以生產環境禁止開啟debug
11.不要既打印了異常日志,又拋出異常
try { // TODO 業務代碼處理 } catch (Exception e) { log.error("xxx業務處理異常", e); // BusinessException:自定義業務處理異常類 throw new BusinessException("xxx業務處理異常", e); }
此處會打印兩次異常日志:
第一次是log.error("xxx業務處理異常", e)會打印一次;
第二次是throw new BusinessException("xxx業務處理異常", e)會打印一次;
可以根據具體情況來選擇,例如此處就應該拋異常處理,那么可以僅使用throw new BusinessException("xxx業務處理異常", e)即可
12.避免重復打印日志
如果一行日志可以表達清楚,則使用一行打印即可,避免日志信息冗余
反例:
log.info("創建用戶信息 userId={}", userId);
log.info("創建用戶信息 userName={}", userName);
正例:
log.info("創建用戶信息 userId={},userName={}", userId, userName);
13.日志文件分離
根據不同的日志級別,打印在不同的日志文件中,例如debug、info、warn、error日志級別的日志分別創建一個日志文件debug.log、info.log、warn.log、error.log進行日志打印;
將不同類型的日志進行日志分類,例如access.log、error.log等
14.核心功能模塊,建議打印較完整的日志
業務系統中,核心功能的代碼,盡可能打印日志完整,核心代碼執行頻率極高,出問題時,根據日志信息能快速定位。