使用 Log4j 的 NDC/MDC 改進日誌

1、問題產生

通常軟件系統都會由簡單到複雜,功能少的,簡單的軟件系統用用通常的日誌輸出也就可以解決問題了,可是這種軟件系統真正能用的也太少了,實際場景中每每是軟件麼越作越複雜,越作越臃腫,且不說軟件系統設計好壞,總歸這是一個軟件系統所要經歷的,那麼在系統中用日誌的方式添加追蹤點,幫助解決問題就成了比較經常使用的方法。而對於網站系統,一般除了知道這個日誌是在哪一個跟蹤點產生的,還須要知道是由誰(每每是哪一個用戶,哪一個 IP 等)產生的,這時就會想到往日誌裏塞入這類信息,但並非每段代碼都那麼容易獲取到用戶的信息,特別是那些分層比較多,比較內層的方法,將一個對象穿透到應用的各個部分,想一想都有點很差辦。那麼用戶相關的信息每個請求都是有辦法獲取到的,那有沒有辦法不侵入系統的內部而實現獲取這些信息呢?原來 Log4j 早就爲咱們準備好了。html

2、瞭解 NDC/MDC

關於 NDC/MDC 的詳細介紹能夠看這裏:在 Web 應用中增長用戶跟蹤功能java

簡而言之:NDC是一種在同一個線程內將信息保存起來,提供日誌輸出時使用。MDC 與 NDC 相比,信息以 Map 方式保存,支付多個健值對。web

使用方式上,NDC 經過在 PatternLayout 增長 %x 獲取信息,而 MDC 經過 %X{key} 獲取對應 key 的信息。 注意下,MDC 是大寫 X,我在後面的試驗開始時使用的小寫的,結果沒法正確輸出shell

3、試着寫一個

思路:經過 filter 將 session 中保存的用戶信息放入 MDC,經過調整 log4j.properties 輸出須要的信息。
apache

//定義 Filter
package net.caiban.pc.erp.filter;

import net.caiban.pc.erp.config.AppConst;
import net.caiban.pc.erp.domain.SessionUser;
//import org.apache.log4j.MDC;  //這裏沒有選擇用 Log4j 的 MDC,而是換成了 slf4j
import org.slf4j.MDC;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * @author mays (mays@caiban.net)
 *
 * created on 2016-5-8
 */
public class Log4jMDCFilter implements Filter {

   @Override
   public void destroy() {
   }

   @Override
   public void doFilter(ServletRequest rq, ServletResponse rp,
         FilterChain chain) throws IOException, ServletException {
      HttpServletRequest request= (HttpServletRequest) rq;
      HttpServletResponse response = (HttpServletResponse) rp;

        try {
            putMDC(request, response); //保存信息
            chain.doFilter(request, response);
        }finally {
            clearMDC(request); //記得 clear 相關信息,不然會致使內存溢出
        }
        return ;
   }

   @Override
   public void init(FilterConfig config) throws ServletException {
   }

    public void putMDC(HttpServletRequest request, HttpServletResponse response){
        MDC.put("remoteAddr", request.getRemoteAddr());
        MDC.put("remoteHost", request.getRemoteHost());
        SessionUser user = (SessionUser) request.getSession().getAttribute(AppConst.SESSION_KEY);
        if(user!=null){
            MDC.put("uid", String.valueOf(user.getUid()));
            MDC.put("cid", String.valueOf(user.getCid()));
            MDC.put("account", user.getAccount());
        }
    }

    public void clearMDC(HttpServletRequest request){
        MDC.clear();
    }
}

log4j 配置session

#############default level and appender####################
log4j.rootCategory=info,stdout

###################appender stdout##########################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Threshold = debug
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m %X{uid} %X{remoteAddr}%n

關於的配置:app

%X{uid} %X{remoteAddr}

隨後隨便找個位置打印一句日誌,效果以下:dom

ERROR [http-nio-8080-exec-8] (ApiController.java:193) - log4j test MDC 6 127.0.0.1

大體的方法就這麼些了,但實際應用中這麼寫仍是不夠的,下面看下能夠作的改進ide

4、能夠繼續改進的地方

  1. 豐富信息:網站或軟件系統應根據本身的須要獲取請求中能夠被拿到的任何信息,並將其放入 MDC,例如URI,Referer等網站

  2. 能夠在 MDC 中將請求的時間截放入,同時能夠利用過慮器統計請求耗時,最終將耗時信息提供給日誌,以便輸出

  3. 日誌輸出應從新組織下,輸出日誌到合適的位置,而不是像我示例那樣直接在 console 中輸出

另外有幾點要注意:

  1. 千萬記得 finally 要清除 MDC 中保存的信息

  2. 我在示例中未使用 Log4j 的 MDC,而是換成了 slf4j 的 MDC,功能沒什麼差異,可是 slf4j 能夠直接使用 MDC.clear(),而 Log4j 的 MDC 須要一個個 remove 放入的 key。另外 slf4j 只有 MDC,沒有 NDC。關於 slf4j MDC 的使用,還能夠看下這篇文章(雖然文章排版實在有點亂):Slf4j MDC 使用和 基於 Logback 的實現分析

相關文章
相關標籤/搜索