SpringBoot項目使用slf4j的MDC日志打點功能(最新推薦)
SpringBoot項目使用slf4j的MDC日志打點功能
物料準備:
1.自定義1個線程MDC打點工具類
2.配置logback打印MDC打點的traceId
3.配置webMVC使用MDC打點
4.配置ThreadPoolTaskExecutor使用MDC打點
5.配置HttpClient使用MDC打點
6.測試MDC日志打點效果
線程mdc打點工具類代碼
package cn.ath.knowwikibackend.mdc; import cn.hutool.core.lang.UUID; import org.slf4j.MDC; import java.util.Map; import java.util.concurrent.Callable; /** * 線程mdc打點工具類 */ public class ThreadMdcUtil { public static void setTRaceIdIfAbsent() { if (MDC.get("traceId") == null) { MDC.put("traceId", UUID.fastUUID().toString()); } } public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) { return () -> { if (context == null) { MDC.clear(); } else { MDC.setContextMap(context); } setTRaceIdIfAbsent(); try { //最終通過 callable.call執(zhí)行線程任務(wù) return callable.call(); } finally { MDC.clear(); } }; } public static Runnable wrap(final Runnable runnable,final Map<String,String> context){ return ()->{ if (context==null){ MDC.clear(); //mdc上下文為空 就清掉mdc }else { MDC.setContextMap(context); // mdc上下文不為空,要設(shè)置上下文為 context } setTRaceIdIfAbsent(); //設(shè)置mdc 記錄traceId try { //最終通過 runnable.run 執(zhí)行線程任務(wù) runnable.run(); }finally { MDC.clear(); } }; } }
配置logback 輸出的日志格式,要輸出mdc里定義的traceId
logging: file: path: ${user.home}/.${spring.application.name}/log/ logback: rollingpolicy: max-file-size: 15MB max-history: 10 pattern: # 注意這里配置的 [%X{traceId}] 即輸出mdc打點的traceId值 console: "%date %level [%thread] [%X{traceId}] %logger{10} [%file : %line] %msg%n" file: "%date %level [%thread] [%X{traceId}] %logger{10} [%file : %line] %msg%n" level: cn.ath.knowwikibackend.rest: info
配置webMVC使用MDC打點
定義MvcTraceInterceptor,來攔截http請求進行mdc打點
package cn.ath.knowwikibackend.mdc; import cn.hutool.core.lang.UUID; import org.slf4j.MDC; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * mvc trace攔截器,作用是 實現(xiàn)trace打點 */ public class MvcTraceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //在請求頭里拿一個 traceId ,拿不到就創(chuàng)建1個 traceId String traceId = request.getHeader("traceId"); if (traceId==null){ traceId = UUID.fastUUID().toString(); } // traceId記錄到mdc中 MDC.put("traceId",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 { //1個http請求 后臺處理完畢后 ,從mdc中移除 traceId MDC.remove("traceId"); } }
使用自定義的MvcTraceInterceptor
package cn.ath.knowwikibackend.mdc; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MvcMdcConfig implements WebMvcConfigurer { /** * 配置spring mvc啟用 mvc trace攔截器,實現(xiàn)trace打點 * @param registry InterceptorRegistry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MvcTraceInterceptor()) .addPathPatterns("/**"); } }
配置ThreadPoolTaskExecutor使用MDC打點
package cn.ath.knowwikibackend.mdc; import org.slf4j.MDC; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.io.Serializable; import java.util.concurrent.*; /** * 異步線程池 的mdc 包裝器,作用是 讓 async 異步任務(wù) 實現(xiàn)trace打點 */ public class AsyncThreadPoolMdcWrapper extends ThreadPoolTaskExecutor implements Serializable { private static final long serialVersionUID = -1530245553055682935L; @Override public void execute(Runnable task) { // Runnable 類型的線程運行時 進行 mdc 打點記錄 super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } @Override public <T> Future<T> submit(Callable<T> task) { // Callable 類型的線程運行時 進行 mdc 打點記錄 return super.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap())); } @Override public Future<?> submit(Runnable task) { // Runnable 類型且返回Future的 線程運行時 進行 mdc 打點記錄 return super.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap())); } }
配置ApacheHttpClient使用MDC打點
package cn.ath.knowwikibackend.mdc; import lombok.extern.slf4j.Slf4j; import org.apache.http.Header; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.protocol.HttpContext; import org.slf4j.MDC; import org.springframework.stereotype.Component; import java.io.IOException; /** * 自定義 HttpRequestInterceptor, * 這里可以對外發(fā)的http請求 追加請求頭/請求參數(shù)之類的配置 * * 這里 在 Header 里追加上本應(yīng)用的traceId ,便于全局排查請求日志 */ @Component @Slf4j public class HttpClientTracedInterceptor implements HttpRequestInterceptor { @Override public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { //從mdc 中取出traceId 然后追加到 外發(fā)的http請求頭里 String traceId = MDC.get("traceId"); if (traceId != null){ httpRequest.addHeader("traceId",traceId); } //打印出所有的http請求頭 for (Header header : httpRequest.getAllHeaders()) { log.info("req header item---->{}",header); } } }
測試MDC日志打點效果
修改之前的異步線程池使用AsyncThreadPoolMdcWrapper 替換掉默認的ThreadPoolTaskExecutor
@Configuration public class AsyncConfig { @Bean("asyncCountTestTaskExecutor") public AsyncThreadPoolMdcWrapper asyncCountTaskTest(){ AsyncThreadPoolMdcWrapper executor = new AsyncThreadPoolMdcWrapper(); //核心線程數(shù)5:線程池創(chuàng)建時候初始化的線程數(shù) executor.setCorePoolSize(5); //最大線程數(shù)10:線程池最大的線程數(shù),只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程 executor.setMaxPoolSize(10); //緩沖隊列100:用來緩沖執(zhí)行任務(wù)的隊列 executor.setQueueCapacity(100); //允許線程的空閑時間60秒:當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀 executor.setKeepAliveSeconds(60); //線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池 executor.setThreadNamePrefix("countTestTaskAsync-"); //線程池滿了后新任務(wù)由 任務(wù)發(fā)起者的線程執(zhí)行 RejectedExecutionHandler callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy(); executor.setRejectedExecutionHandler(callerRunsPolicy); executor.initialize(); return executor; } @Bean("asyncVoidTestTaskExecutor") public AsyncThreadPoolMdcWrapper asyncVoidTaskTest(){ AsyncThreadPoolMdcWrapper executor = new AsyncThreadPoolMdcWrapper(); //核心線程數(shù)5:線程池創(chuàng)建時候初始化的線程數(shù) executor.setCorePoolSize(5); //最大線程數(shù)10:線程池最大的線程數(shù),只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程 executor.setMaxPoolSize(10); //緩沖隊列100:用來緩沖執(zhí)行任務(wù)的隊列 executor.setQueueCapacity(100); //允許線程的空閑時間60秒:當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀 executor.setKeepAliveSeconds(60); //線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池 executor.setThreadNamePrefix("voidTestTaskAsync-"); //線程池滿了后新任務(wù)由 任務(wù)發(fā)起者的線程執(zhí)行 RejectedExecutionHandler callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy(); executor.setRejectedExecutionHandler(callerRunsPolicy); executor.initialize(); return executor; } }
在API接口中測試
@Slf4j @RestController @RequestMapping("/third") public class TestApi { @Autowired HttpClientTracedInterceptor httpClientTracedInterceptor; @Autowired private AsyncAllService asyncAllService @GetMapping("/t1") public String te() throws ExecutionException, InterruptedException, IOException { log.trace("trace---test!!!!!"); log.info("info---test!!!!!"); log.warn("warn---test!!!!!"); log.debug("debug---test!!!!!"); log.error("error---test!!!!!"); //有返回值的async任務(wù) Future<Long> longFuture = asyncAllService.testCount1(); //無返回值的async任務(wù) asyncAllService.testAsync2(); //有返回值的async任務(wù),需要future執(zhí)行后,最后統(tǒng)一get返回值 Long l605 = longFuture.get(); log.info("async task res:{}",l605); //測試使用apache httpclient 外發(fā)1個http請求 String content = "test.dhrth.xxx.zzzdfg.derferf.sregvreg.regetg.esrtg34gf3"; String url = "http://10.1.5.212:8008/api/getresult"; Map<String, String> mapData = new HashMap<String, String>(); mapData.put("type", "NER_RE"); mapData.put("text", content); Map<String, Object> map = new HashMap<String, Object>(); map.put("sid", "re"); map.put("data", mapData); String reqStr = JSON.toJSONString(map); // 獲取httpclient CloseableHttpClient httpclient = HttpClients.custom() .addInterceptorFirst(httpClientTracedInterceptor) .build(); //創(chuàng)建post請求 HttpPost httpPost = new HttpPost(url); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(20*1000) .setConnectionRequestTimeout(1000) .setConnectTimeout(1000).build(); httpPost.setConfig(requestConfig); StringEntity entity = new StringEntity(reqStr, ContentType.APPLICATION_JSON); httpPost.setEntity(entity); CloseableHttpResponse response = httpclient.execute(httpPost); // 得到響應(yīng)信息 int statusCode = response.getStatusLine().getStatusCode(); log.info("http-res-statusCode:{}",statusCode); String respStr = EntityUtils.toString(response.getEntity(), "utf-8"); log.info("http-resp:{}",respStr); return "thirdTestApi測試!"; } }
測試控制臺輸出的日志效果
2023-06-05 15:45:54,228 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.s.l.ReqLogAspect [ReqLogAspect.java : 94] 請求URI:/kwb/third/t1
2023-06-05 15:45:54,251 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.s.l.ReqLogAspect [ReqLogAspect.java : 107] 請求目標類:String cn.ath.knowwikibackend.rest.TestApi.te()
2023-06-05 15:45:54,254 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.r.TestApi [TestApi.java : 60] info---test!!!!!
2023-06-05 15:45:54,254 WARN [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.r.TestApi [TestApi.java : 61] warn---test!!!!!
2023-06-05 15:45:54,255 ERROR [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.r.TestApi [TestApi.java : 63] error---test!!!!!
2023-06-05 15:45:54,299 INFO [countTestTaskAsync-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.a.b.AsyncAllService [AsyncAllService.java : 35] now:2023-06-05 15:45:54
2023-06-05 15:45:54,299 INFO [voidTestTaskAsync-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.a.b.AsyncAllService [AsyncAllService.java : 52] now:2023-06-05 15:45:54
2023-06-05 15:45:54,300 INFO [countTestTaskAsync-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.a.b.AsyncAllService [AsyncAllService.java : 36] ---exec testCount1------------
2023-06-05 15:45:54,300 INFO [voidTestTaskAsync-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.a.b.AsyncAllService [AsyncAllService.java : 53] ---exec testAsync2------------
2023-06-05 15:45:54,300 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.r.TestApi [TestApi.java : 73] async task res:8
2023-06-05 15:45:54,352 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.m.HttpClientTracedInterceptor [HttpClientTracedInterceptor.java : 36] req header item---->traceId: 8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4
2023-06-05 15:45:55,429 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.r.TestApi [TestApi.java : 109] http-res-statusCode:200
2023-06-05 15:45:55,431 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.r.TestApi [TestApi.java : 121] http-resp:{
"status": "OK",
"message": "提取成功",
"result": {}
}
2023-06-05 15:45:55,432 INFO [http-nio-8080-exec-1] [8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4] c.a.k.s.l.ReqLogAspect [ReqLogAspect.java : 125] Around請求耗時:1227ms
從日志中可以看到,1次瀏覽器請求進到app后,從切面就開始mdc打點,之后的異步線程操作 和 外發(fā)http操作 的log日志中都顯示出了本次瀏覽器請求對應(yīng)的后臺日志的打點traceId值為 8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4
到此這篇關(guān)于SpringBoot項目使用slf4j的MDC日志打點功能的文章就介紹到這了,更多相關(guān)SpringBoot日志打點內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 單鏈表數(shù)據(jù)結(jié)構(gòu)的增刪改查教程
這篇文章主要介紹了Java 單鏈表數(shù)據(jù)結(jié)構(gòu)的增刪改查教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10Java 其中翻轉(zhuǎn)字符串的實現(xiàn)方法
這篇文章主要介紹了Java 其中翻轉(zhuǎn)字符串的實現(xiàn)方法,需要的朋友可以參考下2014-02-02基于SpringBoot實現(xiàn)驗證碼功能(兩種驗證碼方式)
這篇文章主要介紹了基于SpringBoot實現(xiàn)驗證碼功能,今天我們介紹的是兩種主流的驗證碼,一種就是進行計算的驗證碼,另外一種就是不需要計算,直接輸入的驗證碼,需要的朋友可以參考下2024-08-08Java實現(xiàn)基于清除后分配規(guī)則的垃圾回收器詳解
垃圾回收是 Java 語言的一項重要特性,自動管理對象內(nèi)存,防止內(nèi)存泄漏和野指針問題,下面我們就來看看如何利用Java實現(xiàn)基于清除后分配規(guī)則的垃圾回收器吧2025-03-03Springmvc nginx實現(xiàn)動靜分離過程詳解
這篇文章主要介紹了Springmvc nginx實現(xiàn)動靜分離過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09