使用MDC快速查詢應(yīng)用接口全部執(zhí)行日志
引言
對于每一個(gè)開發(fā)者來說,查詢接口的執(zhí)行日志都是一個(gè)高頻率的操作,每當(dāng)測試說接口有問題時(shí),我們都需要去服務(wù)器或者日志系統(tǒng)上查報(bào)錯的原因。
一般情況下,我們會通過對應(yīng)的關(guān)鍵字或者接口地址去查詢這個(gè)接口到底報(bào)了什么錯,但是這帶來一個(gè)問題,就是我們可能少打日志或者忘打某些關(guān)鍵字的日志,導(dǎo)致查詢記錄比較麻煩。
那么有沒有一種簡單高效的方法,即使我們在日志中不打印任何關(guān)鍵字,系統(tǒng)會自動生成一個(gè)關(guān)鍵字,讓我們一次性查詢出這個(gè)接口的所有l(wèi)og記錄呢?
MDC
MDC是日志門面框架SLF4J提供的一個(gè)類,可以提供在多線程情況下記錄日志的功能,log4j、logback、log4j2都有對這個(gè)類的實(shí)現(xiàn)。
從本質(zhì)上來說,MDC可以看做一個(gè)ThreadLocal,由于其線程安全的特性,可以讓我們輕松安全的保存數(shù)據(jù)。

MDC主要的API有clear()、get()、put()、remove()方法等,簡潔的api讓我們使用上手基本沒有難度。
如何使用
1,修改日志打印格式
以日志框架logback為例,在logback.xml中,找到日志打印規(guī)則的配置,添加**-%X{reqId}** 屬性,其中reqId可以任意指定,你寫其他的屬性也可以,博主這里演示指定為reqId。
<encoder>
<pattern>-%X{reqId} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %file:%line %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
2,添加過濾器MDCFilter
ps.使用攔截器也可以,效果是一樣的。對每個(gè)接口 做攔截。
@Component
@AllArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class MDCFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
try {
//給每個(gè)請求接口生成一個(gè)requestId
String requestId = RandomUtil.randomNumbers(10);
//這里的reqId就是上面配置的,要保持一致
MDC.put("reqId", "reqId:" + requestId);
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
@Override
public void destroy() {
}
}
經(jīng)過簡單的兩步就配置好了,下面我們看一下效果。
@PostMapping(value = "/mdcTest")
public ResponseEntity<Object> mdcTest(String id, String name) {
log.info("測試日志打印,id={},name={}", id, name);
log.info("測試日志打印1");
log.info("測試日志打印2");
log.info("測試日志打印3");
log.info("測試日志打印4");
return ResponseEntity.ok().build();
}

每一行日志都有一個(gè)關(guān)鍵字reqId:9723829830,這樣我們查詢?nèi)罩緯r(shí)只需要查詢關(guān)鍵字9723829830就可以直接查出來這個(gè)接口所有的執(zhí)行記錄了。
如果想更方便的話,也可以把這個(gè)關(guān)鍵字直接輸出到每一個(gè)接口的響應(yīng)頭或者響應(yīng)體中。
進(jìn)階使用
對于普通的web應(yīng)用我們可以直接攔截每個(gè)接口,自動生成一個(gè)請求id,那么對于微服務(wù)項(xiàng)目,一個(gè)接口可能會產(chǎn)生很多服務(wù)的調(diào)用,那如何一次性查出來所有系統(tǒng)內(nèi)的日志呢?
對于日志的搜集本文暫不考慮,咱們先說如何做請求id的傳遞。
其實(shí)也很簡單,當(dāng)我們有多個(gè)系統(tǒng)間的調(diào)用時(shí),把reqId放到request的header中進(jìn)行傳遞,然后下游系統(tǒng)獲取這個(gè)id就可以了。
比如下方的攔截器:
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上層調(diào)用就從header中取出上層的ID
String traceId = request.getHeader("reqId");
if (traceId == null) {
//如果沒有,就生成一個(gè)默認(rèn)的
traceId = RandomUtil.randomNumbers(10);
}
MDC.put("reqId", traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//調(diào)用結(jié)束后刪除
MDC.remove("reqId");
}
}
MDC存在的一些問題
我們在上文說過,MDC的本質(zhì)是ThreadLocal,它會把數(shù)據(jù)都綁定到當(dāng)前線程上。但是當(dāng)我們使用多線程的時(shí)候,就會帶來一個(gè)數(shù)據(jù)丟失的問題。
所以,我們需要進(jìn)行線程間的數(shù)據(jù)傳遞,保證MDC數(shù)據(jù)不丟失。
以線程池傳遞數(shù)據(jù)為例,ThreadPoolTaskExecutor提供了一個(gè)taskDecorator裝飾器,通過這個(gè)屬性,我們就可以實(shí)現(xiàn)屬性的傳遞。
首先,定義一個(gè)MDCContextDecorator,
public class MDCContextDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String,String> previous = MDC.getCopyOfContextMap();
return () -> {
try {
if (previous != null) {
MDC.setContextMap(previous);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
然后設(shè)置線程池的taskDecorator屬性,
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
//...其他屬性
//設(shè)置線程屬性的自動傳遞
executor.setTaskDecorator(new MDCContextDecorator());
return executor;
}
最后
MDC的使用其實(shí)很簡單,對于我們查詢?nèi)罩疽埠苡袔椭?,?yīng)用也算是非常廣泛了。有興趣的同學(xué)也可以去看一下它的內(nèi)部實(shí)現(xiàn),代碼也并不復(fù)雜。
以上就是使用MDC快速查詢應(yīng)用接口全部執(zhí)行日志的詳細(xì)內(nèi)容,更多關(guān)于MDC查詢接口執(zhí)行日志的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA運(yùn)行SpringBoot項(xiàng)目的超詳細(xì)步驟截圖
在當(dāng)前的開發(fā)中Spring Boot開發(fā)框架已經(jīng)成為主流,下面這篇文章主要給大家介紹了關(guān)于IDEA運(yùn)行SpringBoot項(xiàng)目的超詳細(xì)步驟截圖,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
SpringBoot下使用自定義監(jiān)聽事件的流程分析
事件機(jī)制是Spring的一個(gè)功能,目前我們使用了SpringBoot框架,所以記錄下事件機(jī)制在SpringBoot框架下的使用,同時(shí)實(shí)現(xiàn)異步處理,這篇文章主要介紹了SpringBoot下使用自定義監(jiān)聽事件,需要的朋友可以參考下2023-08-08
Java實(shí)現(xiàn)隊(duì)列的三種方法集合
這篇文章主要介紹了Java實(shí)現(xiàn)隊(duì)列的三種方法集合,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java線程池隊(duì)列PriorityBlockingQueue原理分析
這篇文章主要介紹了Java線程池隊(duì)列PriorityBlockingQueue原理分析,PriorityBlockingQueue隊(duì)列是?JDK1.5?的時(shí)候出來的一個(gè)阻塞隊(duì)列,但是該隊(duì)列入隊(duì)的時(shí)候是不會阻塞的,永遠(yuǎn)會加到隊(duì)尾,需要的朋友可以參考下2023-12-12
解讀Jvm的內(nèi)存結(jié)構(gòu)與GC及jvm參數(shù)調(diào)優(yōu)
這篇文章主要介紹了解讀Jvm的內(nèi)存結(jié)構(gòu)與GC及jvm參數(shù)調(diào)優(yōu)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
Spring Boot 項(xiàng)目啟動失敗的解決方案
這篇文章主要介紹了Spring Boot 項(xiàng)目啟動失敗的解決方案,幫助大家更好的理解和學(xué)習(xí)使用Spring Boot,感興趣的朋友可以了解下2021-03-03

