背景
現(xiàn)在大多數(shù)企業(yè)開發(fā)的系統(tǒng)都是分布式系統(tǒng)了辜窑,隨著系統(tǒng)的復(fù)雜如何有效地追蹤定位線上問題也變得更加困難讹剔。我們可以使用requestId(traceId)來解決這一問題头岔。
實現(xiàn)思路
- 使用過濾器或切面在每個HTTP請求進到Controller前生成一個唯一標識請求的requestId古戴,可以使用UUID,放在ThreadLocal里摄悯,方便上下文調(diào)用赞季。
public static final String REQUEST_ID_KEY = "requestId";
public static ThreadLocal<String> requestIdThreadLocal = new ThreadLocal<String>();
private static final Logger logger = LoggerFactory.getLogger(RequestIdUtil.class);
public static String getRequestId(HttpServletRequest request) {
String requestId = null;
String parameterRequestId = request.getParameter(REQUEST_ID_KEY);
String headerRequestId = request.getHeader(REQUEST_ID_KEY);
if (parameterRequestId == null && headerRequestId == null) {
logger.info("request parameter 和header 都沒有requestId入?yún)?);
requestId = UUID.randomUUID().toString();
} else {
requestId = parameterRequestId != null ? parameterRequestId : headerRequestId;
}
requestIdThreadLocal.set(requestId);
return requestId;
}
- requestId結(jié)合日志打印,這里使用slf4j標準里的MDC實現(xiàn)奢驯,例子使用logback打印日志申钩。
代碼使用:
MDC.put("requestId", requestId);
logback配置示例:
<configuration>
<appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Encoding>UTF-8</Encoding>
<File>${log_base}/java-base-web.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log_base}/java-base-web-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
<MaxHistory>10</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>200MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d^|^%X{requestId}^|^%-5level^|^%logger{36}%M^|^%msg%n</pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="logfile" />
</root>
</configuration>
主要是在pattern那里指定,使用MDC里面的變量用的是%X{requestId}瘪阁,requestId為放進MDC里面的key撒遣,
達到的效果就是打印出來的日志自動帶上了requestId。效果如下:
[a0b3589f-fe87-4d3a-9151-8925afd0a6c6] at com.test.demo.TestMain2.test2(TestMain2.java:26)
相當(dāng)于切日志管跺,這樣我們線上就可以使用requestId在ELK上搜索屬于這個請求的日志了义黎。
- 使用HTTP Client或者OKHTTP等在后臺請求其他系統(tǒng)的服務(wù)的時候把放在上下文的requestId放在請求參數(shù)或者頭里,推薦放在header豁跑,上面的切面一開始會判斷requestId是否在請求里面廉涕,有的話就繼續(xù)沿用調(diào)用方的requestId,這樣子在被調(diào)用的系統(tǒng)也能追蹤到屬于同一個調(diào)用鏈的日志了艇拍。
String requestId = RequestIdUtil.requestIdThreadLocal.get();
headerMap.put(RequestIdUtil.REQUEST_ID_KEY, requestId);
Map<String, String> paramMap = new HashMap<String, String>();
String resultString = JsonHttpClientUtil.post(testHttpClientUrl, headerMap, paramMap, "UTF-8");
logger.info(resultString);
- requestId可以放到一個封裝響應(yīng)類里面狐蜕,這樣子我們就可以在控制臺里面看到響應(yīng)參數(shù)里面的requestId,方便在ELK里面查找日志了卸夕。也可以結(jié)合異常體系使用层释,拋的異常打印的日志自然也帶上了requestId,同時可以使用釘釘機器人來通知開發(fā)區(qū)定位問題快集,同時帶上requestId和異常堆棧信息就行了湃累。這樣就能方便的知道異常發(fā)生的上下文日志了。
總結(jié)
上面總結(jié)了一些工作中使用起來比較便利的定位問題的方法體系碍讨。希望可以給讀者帶來一些啟示治力。