阿里Java開發手冊思考(四)

史培培發表於2018-02-08

題圖:by pixel2013 From pixabay

上期我們分享了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地址),如下圖:

Log4j Web Tracker

相關配置:

<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的日誌都能實現視覺化以及動態修改日誌級別的功能

阿里Java開發手冊思考(四)

微信公眾號:碼上論劍
請關注我的個人技術微信公眾號,訂閱更多內容

相關文章