
一、日志打印級別
-
DEBUG(調(diào)試)
開發(fā)調(diào)試日志。一般來說,在系統(tǒng)實際運行過程中,不會輸出該級別的日志。因此,開發(fā)人員可以打印任何自己覺得有利于了解系統(tǒng)運行狀態(tài)的東東。不過很多場景下,過多的DEBUG日志,并不是好事,建議是按照業(yè)務(wù)邏輯的走向打印。 -
INFO(通知)
INFO日志級別主要用于記錄系統(tǒng)運行狀態(tài)等關(guān)聯(lián)信息。該日志級別,常用于反饋系統(tǒng)當前狀態(tài)給最終用戶。所以,在這里輸出的信息,應(yīng)該對最終用戶具有實際意義,也就是最終用戶要能夠看得明白是什么意思才行。 -
WARN(警告)
WARN日志常用來表示系統(tǒng)模塊發(fā)生問題,但并不影響系統(tǒng)運行。 此時,進行一些修復(fù)性的工作,還能把系統(tǒng)恢復(fù)到正常的狀態(tài)。 -
ERROR(錯誤)
此信息輸出后,主體系統(tǒng)核心模塊正常工作,需要修復(fù)才能正常工作。 就是說可以進行一些修復(fù)性的工作,但無法確定系統(tǒng)會正常的工作下去,系統(tǒng)在以后的某個階段,很可能會因為當前的這個問題,導(dǎo)致一個無法修復(fù)的錯誤(例如宕機),但也可能一直工作到停止也不出現(xiàn)嚴重問題。
二、日志打印規(guī)范
1. 【強制】應(yīng)用中不可直接使用日志系統(tǒng) (Log 4 j 、 Logback) 中的 API ,而應(yīng)依賴使用日志框架
SLF 4 J 中的 API ,使用門面模式的日志框架,有利于維護和各個類的日志處理方式統(tǒng)一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
- 【強制】日志文件推薦至少保存 15 天,因為有些異常具備以“周”為頻次發(fā)生的特點。
可以結(jié)合實際業(yè)務(wù)需求,基于按天,和按照容量配置appender。例如,按天保存接口對接基本關(guān)鍵數(shù)值記錄日志,按照容量保存接口對接詳細日志。
- 【強制】應(yīng)用中的擴展日志 ( 如打點、臨時監(jiān)控、訪問日志等 )
- 命名方式:appName _ logType _ logName . log 。
- 日志類型( logType),推薦分類有stats / desc / monitor / visit 等。
- 日志描述(logName)。
這種命名的好處:通過文件名就可知道日志文件屬于什么應(yīng)用,什么類型,什么目的,也有利于歸類查找。推薦對日志進行分類,如將錯誤日志和業(yè)務(wù)日志分開存放,便于開發(fā)人員查看,也便于通過日志對系統(tǒng)進行及時監(jiān)控。
正例: mppserver 應(yīng)用中單獨監(jiān)控時區(qū)轉(zhuǎn)換異常,如:mppserver _ monitor _ timeZoneConvert . log
- 【強制】對 trace / debug / info 級別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明: logger . debug( " Processing trade with id : " + id + " and symbol : " + symbol)。如果日志級別是 warn ,上述日志不會打印,但是會執(zhí)行字符串拼接操作,如果 symbol 是對象,會執(zhí)行 toString() 方法,浪費了系統(tǒng)資源,執(zhí)行了上述操作,最終日志卻沒有打印。
正例: ( 占位符 )
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
- 【強制】避免重復(fù)打印日志,浪費磁盤空間,務(wù)必在 log 4 j . xml 中設(shè)置 additivity = false 。
正例: <logger name="com.taobao.dubbo.config" additivity="false">
- 【強制】異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場信息和異常堆棧信息。如果不處理,那么通過關(guān)鍵字 throws 往上拋出。
正例: logger.error(各類參數(shù)或者對象 toString + "_" + e.getMessage(), e);
- 【推薦】謹慎地記錄日志。生產(chǎn)環(huán)境禁止輸出 debug 日志 ; 有選擇地輸出 info 日志 ; 如果使用 warn 來記錄剛上線時的業(yè)務(wù)行為信息,一定要注意日志輸出量的問題,避免把服務(wù)器磁盤撐爆,并記得及時刪除這些觀察日志。
說明:大量地輸出無效日志,不利于系統(tǒng)性能提升,也不利于快速定位錯誤點。記錄日志時請思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
- 【參考】可以使用 warn 日志級別來記錄用戶輸入?yún)?shù)錯誤的情況,避免用戶投訴時,無所適從。注意日志輸出的級別, error 級別只記錄系統(tǒng)邏輯出錯、異常等重要的錯誤信息。如非必要,請不要在此場景打出 error 級別。
三、日志打印技巧
問題排查的日志
-
對接外部的調(diào)用封裝:
程序中對接外部系統(tǒng)與模塊的依賴調(diào)用前后都記下日志,方便接口調(diào)試。出問題時也可以很快理清是哪塊的問題
LOG.debug("Calling external system:" + parameters);
Object result = null;
try {
result = callRemoteSystem(params);
LOG.debug("Called successfully. result is " + result);
} catch (Exception e) {
LOG.warn("Failed at calling xxx system . exception : " + e);
}
- 狀態(tài)變化:
程序中重要的狀態(tài)信息的變化應(yīng)該記錄下來,方便查問題時還原現(xiàn)場,推斷程序運行過程
boolean isRunning = true;
LOG.info("System is running");
//...
isRunning = false;
LOG.info("System was interrupted by " + Thread.currentThread().getName());
- 系統(tǒng)入口與出口:
這個粒度可以是重要方法級或模塊級。記錄它的輸入與輸出,方便定位
void execute(Object input) {
LOG.debug("Invoke parames : " + input);
Object result = null;
//business logical
LOG.debug("Method result : " + result);
}
- 業(yè)務(wù)異常:
任何業(yè)務(wù)異常都應(yīng)該記下來
try {
//business logical
} catch (IOException e) {
LOG.warn("Description xxx" , e);
} catch (BusinessException e) {
LOG.warn("Let me know anything",e);
} catch (Exception e) {
LOG.error("Description xxx", e);
}
void invoke(Object primaryParam) {
if (primaryParam == null) {
LOG.warn(原因...);
return;
}
}
- 非預(yù)期執(zhí)行:
為程序在“有可能”執(zhí)行到的地方打印日志。如果我想刪除一個文件,結(jié)果返回成功。但事實上,那個文件在你想刪除之前就不存在了。最終結(jié)果是一致的,但程序得讓我們知道這種情況,要查清為什么文件在刪除之前就已經(jīng)不存在呢
int myValue = xxxx;
int absResult = Math.abs(myValue);
if (absResult < 0) {
LOG.info("Original int " + myValue + "has nagetive abs " + absResult);
}
- 很少出現(xiàn)的else情況:
else可能吞掉你的請求,或是賦予難以理解的最終結(jié)果
Object result = null;
if (running) {
result = xxx;
} else {
result = yyy;
LOG.debug("System does not running, we change the final result");
}
程序運行狀態(tài)的日志
程序在運行時就像一個機器人,我們可以從它的日志看出它正在做什么,是不是按預(yù)期的設(shè)計在做,所以這些正常的運行狀態(tài)是要有的。
- 程序運行時間:
long startTime = System.currentTime();
// business logical
LOG.info("execution cost : " + (System.currentTime() - startTime) + "ms");
大批量數(shù)據(jù)的執(zhí)行進度:
LOG.debug("current progress: " + (currentPos * 100 / totalAmount) + "%");
關(guān)鍵變量及正在做哪些重要的事情:
執(zhí)行關(guān)鍵的邏輯,做IO操作等等
String getJVMPid() {
String pid = "";
// Obtains JVM process ID
LOG.info("JVM pid is " + pid);
return pid;
}
void invokeRemoteMethod(Object params) {
LOG.info("Calling remote method : " + params);
//Calling remote server
}
四、需要規(guī)避的問題
頻繁打印大數(shù)據(jù)量日志:
當日志產(chǎn)生的速度大于日志文件寫磁盤的速度,會導(dǎo)致日志內(nèi)容積壓在內(nèi)存中,導(dǎo)致內(nèi)存泄漏。無意義的Log:
日志不包含有意義的信息: 你肯定想知道的是哪個文件不存在吧
File file = new File("xxx");
if (!file.isExist()) {
LOG.warn("File does not exist"); //Useless message
}
- 混淆信息的Log:
日志應(yīng)該是清晰準確的: 當看到日志的時候,你知道是因為連接池取不到連接導(dǎo)致的問題么?
Connection connection = ConnectionFactory.getConnection();
if (connection == null) {
LOG.warn("System initialized unsuccessfully");
}
參考:
《阿里巴巴開發(fā)手冊》
Logger日志級別說明及設(shè)置方法、說明
閑談程序中如何打印log