上期我們分享了Java中日誌的處理(上):Java中日誌的相關知識、Slf4j的原理及原始碼分析
本期我們將分享Java中日誌的處理(下)
首先看下阿里Java開發手冊中日誌規約的剩餘幾條並給出分析:
- 2.【強制】日誌檔案推薦至少儲存 15 天,因為有些異常具備以“周”為頻次發生的特點。
分析:
- 如果使用的是
Log4j
,且採用的RollingFileAppender
方式, 通過設定maxBackupIndex
屬性來指定要保留的日誌檔案數的最大值可以間接實現刪除N天前的日誌檔案- 如果使用的是
Log4j
,且採用的DailyRollingFileAppender
方式,由於該方式不支援maxBackupIndex
,需要重新實現DailyRollingFileAppender
,用以支援maxBackupIndex
的設定- 如果使用的是
Logback
,可以通過設定maxHistory
實現刪除N天前的日誌
-
3.【強制】應用中的擴充套件日誌(如打點、臨時監控、訪問日誌等)命名方式: appName_logType_logName.log。logType:日誌型別,推薦分類有stats/desc/monitor/visit 等;logName:日誌 述。這種命名的好處:通過檔名就可知道日誌檔案屬於什麼應用,什麼型別,什麼目的,也有利於歸類查詢。 正例:mppserver應用中單獨監控時區轉換異常,如: mppserver_monitor_timeZoneConvert.log 說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員檢視,也便於 通過日誌對系統進行及時監控。
-
4.【強制】對trace/debug/info級別的日誌輸出,必須使用條件輸出形式或者使用佔位符的方 式。 說明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日誌級別是warn,上述日誌不會列印,但是會執行字串拼接操作,如果 symbol是物件, 會執行toString()方法,浪費了系統資源,執行了上述操作,最終日誌卻沒有列印。
正例:(條件)
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
複製程式碼
正例:(佔位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
複製程式碼
分析:
- 正如上篇分析的,推薦所有使用
Slf4j
,列印日誌統一使用佔位符
,且不需判讀isxxxEnabled()
- 5.【強制】避免重複列印日誌,浪費磁碟空間,務必在 log4j.xml 中設定 additivity=false。 正例:
<logger name="com.taobao.dubbo.config" additivity="false">
複製程式碼
- 6.【強制】異常資訊應該包括兩類資訊:案發現場資訊和異常堆疊資訊。如果不處理,那麼通過 關鍵字 throws 往上丟擲。 正例:
logger.error(各類引數或者物件 toString + "_" + e.getMessage(), e);
複製程式碼
- 7.【推薦】謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info日誌;如果使 用 warn 來記錄剛上線時的業務行為資訊,一定要注意日誌輸出量的問題,避免把伺服器磁碟 撐爆,並記得及時刪除這些觀察日誌。 說明:大量地輸出無效日誌,不利於系統效能升,也不利於快速定位錯誤點。記錄日誌時請 思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能給問題排查帶來好處?
- 8.【參考】可以使用warn 日誌級別來記錄使用者輸入引數錯誤的情況,避免使用者投訴時,無所適 從。注意日誌輸出的級別,error級別只記錄系統邏輯出錯、異常等重要的錯誤資訊。如非必 要,請不要在此場景打出 error級別。
補充
- 1、 涉及到多執行緒時,日誌中最好將執行緒id列印出來,以區分不同的執行緒
public final class LogIdThreadLocal {
private static ThreadLocal<String> logIdThreadLocal = new ThreadLocal<String>();
...
}
public class MyPatternLayout extends PatternLayout {
private static final String SPLIT_STRING = "|";
@Override
public String format(LoggingEvent event) {
String log = super.format(event);
String threadLocalId = LogIdThreadLocal.getLogId();
if (StringUtils.isEmpty(threadLocalId)) {
threadLocalId = LogIdThreadLocal.create();
}
return log + threadLocalId + SPLIT_STRING + event.getMessage() + Layout.LINE_SEP;
}
}
複製程式碼
log4j.appender.output.layout=com.test.log.MyPatternLayout
log4j.appender.output.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSS}|%t|%-5p|%C{1}.%M:%L|
複製程式碼
- 2、 提供動態修改日誌級別的介面或者使用Log4j Web Tracker
對於生產環境,預設的日誌級別可能是error/warning/info,對於debug的日誌就沒有打出來,如果要讓debug日誌能列印出來,那麼常見的方法就是修改log4j.xml或者log4j.properties檔案,修改了之後需要重啟tomcat,我們知道,生產環境是不可能隨隨便便重啟的,那麼有沒有其他方法呢?答案是:有。 Log4j為我們提供了這樣的API,通過呼叫Log4j的API,提供rest介面,使得客戶端可以動態修改某個日誌的級別:
private String changeLoggerLevel(String loggerName, String level) {
Logger logger = LogManager.exists(loggerName);
String result = null;
if (logger != null) {
logger.setLevel(Level.toLevel(level));
result = logger.getName() + "|" + logger.getLevel();
} else {
result = "logger not exist.";
}
return result;
}
複製程式碼
此外,推薦一個開源的第三方元件:Log4j Web Track(連結為Github地址),如下圖:
相關配置:
<servlet>
<servlet-name>TrackerServlet</servlet-name>
<servlet-class>log4jwebtracker.servlet.TrackerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TrackerServlet</servlet-name>
<url-pattern>/tracker/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Log4jInitServlet</servlet-name>
<servlet-class>log4jwebtracker.servlet.init.Log4jInitServlet</servlet-class>
<init-param>
<param-name>log4jConfigLocation</param-name>
<param-value>WEB-INF/classes/log4j.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
複製程式碼
通過閱讀原始碼,其也是呼叫了Log4j的API,進行了展示:
public abstract class LoggingUtils {
static synchronized public List getFileAppenders() {
List list = new ArrayList();
Enumeration e = LogManager.getRootLogger().getAllAppenders();
while(e.hasMoreElements()) {
Appender a = (Appender) e.nextElement();
if(a instanceof FileAppender) {
list.add(a);
}
}
return list;
}
static synchronized public FileAppender getFileAppender(String appenderName) {
Enumeration e = LogManager.getRootLogger().getAllAppenders();
while(e.hasMoreElements()) {
Appender a = (Appender) e.nextElement();
if(a instanceof FileAppender && a.getName().equals(appenderName)) {
return (FileAppender) a;
}
}
return null;
}
static public boolean contains(List loggers, String loggerName) {
int i=0;
while(i<loggers.size()) {
if(((Logger)loggers.get(i)).getName().equals(loggerName)) {
return true;
}
i++;
}
return false;
}
static public List getLoggers() {
Enumeration e = LogManager.getCurrentLoggers();
List loggersList = new LinkedList();
while(e.hasMoreElements()) {
loggersList.add(e.nextElement());
}
Collections.sort(loggersList, new Comparator() {
public int compare(Object arg0, Object arg1) {
Logger log0 = (Logger) arg0;
Logger log1 = (Logger) arg1;
return log0.getName().compareTo(log1.getName());
}
});
loggersList.add(0, LogManager.getRootLogger());
return loggersList;
}
}
複製程式碼
如果有興趣的話,我們可以實現一個Web Tracker的門面,類似於Slf4j,那麼對於Log4j1/2、LogBack、Juc、Commons Logging的日誌都能實現視覺化以及動態修改日誌級別的功能