SpringBoot項(xiàng)目使用slf4j的MDC日志打點(diǎn)功能(最新推薦)
SpringBoot項(xiàng)目使用slf4j的MDC日志打點(diǎn)功能
物料準(zhǔn)備:
1.自定義1個(gè)線程MDC打點(diǎn)工具類(lèi)
2.配置logback打印MDC打點(diǎn)的traceId
3.配置webMVC使用MDC打點(diǎn)
4.配置ThreadPoolTaskExecutor使用MDC打點(diǎn)
5.配置HttpClient使用MDC打點(diǎn)
6.測(cè)試MDC日志打點(diǎn)效果
線程mdc打點(diǎn)工具類(lèi)代碼
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打點(diǎn)工具類(lèi)
*/
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 {
//最終通過(guò) 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 {
//最終通過(guò) 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打點(diǎn)的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打點(diǎn)
定義MvcTraceInterceptor,來(lái)攔截http請(qǐng)求進(jìn)行mdc打點(diǎn)
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攔截器,作用是 實(shí)現(xiàn)trace打點(diǎn)
*/
public class MvcTraceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在請(qǐng)求頭里拿一個(gè) traceId ,拿不到就創(chuàng)建1個(gè) 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個(gè)http請(qǐng)求 后臺(tái)處理完畢后 ,從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攔截器,實(shí)現(xiàn)trace打點(diǎn)
* @param registry InterceptorRegistry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MvcTraceInterceptor())
.addPathPatterns("/**");
}
}配置ThreadPoolTaskExecutor使用MDC打點(diǎn)
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ù) 實(shí)現(xiàn)trace打點(diǎn)
*/
public class AsyncThreadPoolMdcWrapper extends ThreadPoolTaskExecutor implements Serializable {
private static final long serialVersionUID = -1530245553055682935L;
@Override
public void execute(Runnable task) {
// Runnable 類(lèi)型的線程運(yùn)行時(shí) 進(jìn)行 mdc 打點(diǎn)記錄
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
// Callable 類(lèi)型的線程運(yùn)行時(shí) 進(jìn)行 mdc 打點(diǎn)記錄
return super.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}
@Override
public Future<?> submit(Runnable task) {
// Runnable 類(lèi)型且返回Future的 線程運(yùn)行時(shí) 進(jìn)行 mdc 打點(diǎn)記錄
return super.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));
}
}配置ApacheHttpClient使用MDC打點(diǎn)
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,
* 這里可以對(duì)外發(fā)的http請(qǐng)求 追加請(qǐng)求頭/請(qǐng)求參數(shù)之類(lèi)的配置
*
* 這里 在 Header 里追加上本應(yīng)用的traceId ,便于全局排查請(qǐng)求日志
*/
@Component
@Slf4j
public class HttpClientTracedInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
//從mdc 中取出traceId 然后追加到 外發(fā)的http請(qǐng)求頭里
String traceId = MDC.get("traceId");
if (traceId != null){
httpRequest.addHeader("traceId",traceId);
}
//打印出所有的http請(qǐng)求頭
for (Header header : httpRequest.getAllHeaders()) {
log.info("req header item---->{}",header);
}
}
}測(cè)試MDC日志打點(diǎn)效果
修改之前的異步線程池使用AsyncThreadPoolMdcWrapper 替換掉默認(rèn)的ThreadPoolTaskExecutor
@Configuration
public class AsyncConfig {
@Bean("asyncCountTestTaskExecutor")
public AsyncThreadPoolMdcWrapper asyncCountTaskTest(){
AsyncThreadPoolMdcWrapper executor = new AsyncThreadPoolMdcWrapper();
//核心線程數(shù)5:線程池創(chuàng)建時(shí)候初始化的線程數(shù)
executor.setCorePoolSize(5);
//最大線程數(shù)10:線程池最大的線程數(shù),只有在緩沖隊(duì)列滿(mǎn)了之后才會(huì)申請(qǐng)超過(guò)核心線程數(shù)的線程
executor.setMaxPoolSize(10);
//緩沖隊(duì)列100:用來(lái)緩沖執(zhí)行任務(wù)的隊(duì)列
executor.setQueueCapacity(100);
//允許線程的空閑時(shí)間60秒:當(dāng)超過(guò)了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷(xiāo)毀
executor.setKeepAliveSeconds(60);
//線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
executor.setThreadNamePrefix("countTestTaskAsync-");
//線程池滿(mǎn)了后新任務(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í)候初始化的線程數(shù)
executor.setCorePoolSize(5);
//最大線程數(shù)10:線程池最大的線程數(shù),只有在緩沖隊(duì)列滿(mǎn)了之后才會(huì)申請(qǐng)超過(guò)核心線程數(shù)的線程
executor.setMaxPoolSize(10);
//緩沖隊(duì)列100:用來(lái)緩沖執(zhí)行任務(wù)的隊(duì)列
executor.setQueueCapacity(100);
//允許線程的空閑時(shí)間60秒:當(dāng)超過(guò)了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷(xiāo)毀
executor.setKeepAliveSeconds(60);
//線程池名的前綴:設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
executor.setThreadNamePrefix("voidTestTaskAsync-");
//線程池滿(mǎn)了后新任務(wù)由 任務(wù)發(fā)起者的線程執(zhí)行
RejectedExecutionHandler callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
executor.setRejectedExecutionHandler(callerRunsPolicy);
executor.initialize();
return executor;
}
}在API接口中測(cè)試
@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();
//無(wú)返回值的async任務(wù)
asyncAllService.testAsync2();
//有返回值的async任務(wù),需要future執(zhí)行后,最后統(tǒng)一get返回值
Long l605 = longFuture.get();
log.info("async task res:{}",l605);
//測(cè)試使用apache httpclient 外發(fā)1個(gè)http請(qǐng)求
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請(qǐng)求
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測(cè)試!";
}
} 測(cè)試控制臺(tái)輸出的日志效果
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] 請(qǐng)求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] 請(qǐng)求目標(biāo)類(lèi):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請(qǐng)求耗時(shí):1227ms
從日志中可以看到,1次瀏覽器請(qǐng)求進(jìn)到app后,從切面就開(kāi)始mdc打點(diǎn),之后的異步線程操作 和 外發(fā)http操作 的log日志中都顯示出了本次瀏覽器請(qǐng)求對(duì)應(yīng)的后臺(tái)日志的打點(diǎn)traceId值為 8e5e137d-1bb0-49bc-b7da-c8b9f6568ef4
到此這篇關(guān)于SpringBoot項(xiàng)目使用slf4j的MDC日志打點(diǎn)功能的文章就介紹到這了,更多相關(guān)SpringBoot日志打點(diǎn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法
- SpringBoot利用MDC機(jī)制過(guò)濾單次請(qǐng)求的所有日志
- SpringBoot MDC全鏈路調(diào)用日志跟蹤實(shí)現(xiàn)詳解
- SpringBoot+MDC實(shí)現(xiàn)鏈路調(diào)用日志的方法
- Springboot+MDC+traceId日志中打印唯一traceId
- SpringBoot 項(xiàng)目添加 MDC 日志鏈路追蹤的執(zhí)行流程
- SpringBoot項(xiàng)目使用MDC給日志增加唯一標(biāo)識(shí)的實(shí)現(xiàn)步驟
相關(guān)文章
Java 單鏈表數(shù)據(jù)結(jié)構(gòu)的增刪改查教程
這篇文章主要介紹了Java 單鏈表數(shù)據(jù)結(jié)構(gòu)的增刪改查教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
Java 其中翻轉(zhuǎn)字符串的實(shí)現(xiàn)方法
這篇文章主要介紹了Java 其中翻轉(zhuǎn)字符串的實(shí)現(xiàn)方法,需要的朋友可以參考下2014-02-02
Java一些常見(jiàn)的出錯(cuò)異常處理方法總結(jié)
下面小編就為大家?guī)?lái)一篇Java一些常見(jiàn)的出錯(cuò)異常處理方法總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
基于SpringBoot實(shí)現(xiàn)驗(yàn)證碼功能(兩種驗(yàn)證碼方式)
這篇文章主要介紹了基于SpringBoot實(shí)現(xiàn)驗(yàn)證碼功能,今天我們介紹的是兩種主流的驗(yàn)證碼,一種就是進(jìn)行計(jì)算的驗(yàn)證碼,另外一種就是不需要計(jì)算,直接輸入的驗(yàn)證碼,需要的朋友可以參考下2024-08-08
Java實(shí)現(xiàn)基于清除后分配規(guī)則的垃圾回收器詳解
垃圾回收是 Java 語(yǔ)言的一項(xiàng)重要特性,自動(dòng)管理對(duì)象內(nèi)存,防止內(nèi)存泄漏和野指針問(wèn)題,下面我們就來(lái)看看如何利用Java實(shí)現(xiàn)基于清除后分配規(guī)則的垃圾回收器吧2025-03-03
Springmvc nginx實(shí)現(xiàn)動(dòng)靜分離過(guò)程詳解
這篇文章主要介紹了Springmvc nginx實(shí)現(xiàn)動(dòng)靜分離過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
springboot注冊(cè)攔截器所遇到的問(wèn)題
這篇文章主要介紹了springboot注冊(cè)攔截器的方法及所遇到的問(wèn)題,需要的朋友可以參考下2018-07-07

