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

題圖:by pixel2013 From pixabay

上期咱們分享了Java中日誌的處理(上):Java中日誌的相關知識、Slf4j的原理及源碼分析java

本期咱們將分享Java中日誌的處理(下)git

首先看下阿里Java開發手冊中日誌規約的剩餘幾條並給出分析:

  • 2.【強制】日誌文件推薦至少保存 15 天,由於有些異常具有以「周」爲頻次發生的特色。

分析:github

  • 若是使用的是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 說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於 經過日誌對系統進行及時監控。web

  • 4.【強制】對trace/debug/info級別的日誌輸出,必須使用條件輸出形式或者使用佔位符的方 式。 說明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 若是日誌級別是warn,上述日誌不會打印,可是會執行字符串拼接操做,若是 symbol是對象, 會執行toString()方法,浪費了系統資源,執行了上述操做,最終日誌卻沒有打印。tomcat

正例:(條件)服務器

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級別。

補充

  • 一、 涉及到多線程時,日誌中最好將線程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| 
複製代碼
  • 二、 提供動態修改日誌級別的接口或者使用Log4j Web Tracker

對於生產環境,默認的日誌級別多是error/warning/info,對於debug的日誌就沒有打出來,若是要讓debug日誌能打印出來,那麼常見的方法就是修改log4j.xml或者log4j.properties文件,修改了以後須要重啓tomcat,咱們知道,生產環境是不可能隨隨便便重啓的,那麼有沒有其餘方法呢?答案是:有。 Log4j爲咱們提供了這樣的API,經過調用Log4j的API,提供rest接口,使得客戶端能夠動態修改某個日誌的級別:app

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地址),以下圖:ide

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/二、LogBack、Juc、Commons Logging的日誌都能實現可視化以及動態修改日誌級別的功能

微信公衆號: 碼上論劍
請關注個人我的技術微信公衆號,訂閱更多內容
相關文章
相關標籤/搜索