不會為每個請求新增獨一無二的id?輕鬆改造spring專案
前言-為什麼要新增id?
1.新建專案
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
除了spring的web依賴包外,新增一個lombok,方便編碼。
2.編寫controller方法
@RestController
public class TestController {
@GetMapping("hello")
public String hello(){
log.info("this is hello 1");
log.info("this is hello 2");
log.info("this is hello 3");
return "hello";
}}
這個時候一個簡單的rest介面已經完成了,我們可以先看看日誌的效果。
2019-10-23 10:45:10.917 INFO 1146 --- [nio-8080-exec-1] c.e.demo.controller.TestController : this is hello 1
2019-10-23 10:45:10.917 INFO 1146 --- [nio-8080-exec-1] c.e.demo.controller.TestController : this is hello 2
2019-10-23 10:45:10.917 INFO 1146 --- [nio-8080-exec-1] c.e.demo.controller.TestController : this is hello 3
從這個日誌中,我們根本無法區分單獨的請求。如果同一時間有2到3個請求過來的話,那麼你還能分得清哪個對哪個麼?所以這就是今天要改造的地方。
改造思路
其實要改造的話其實很簡單,我們可以在每個controller入口處,生成唯一的uuid,並傳遞下去。這樣的話缺點就是對程式碼干擾太大,每個方法都要多加一個引數。
那麼我們能不能把這個引數存在一個統一的地方,需要列印日誌的時候,直接去取呢?大家應該可以想到了,用ThreadLocal類。其實到這兒思路已經對了,不過日誌框架也想到了這個問題,他們已經封裝好了現成的功能,就是日誌框架中的MDC
1.首先將日誌的id新增進MDC中
@Component
public class TraceIdInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = getTraceId(request); MDC.put("traceId", traceId);
//將traceId新增進響應頭
response.addHeader("traceId",traceId);
return true;
}
private String getTraceId(HttpServletRequest request){
return String.format("%s - %s",request.getRequestURI(), UUID.randomUUID());
}
}
@Component
public class GlobalWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private TraceIdInterceptor traceIdInterceptor;
@Override
/*
traceId 攔截器需要在最開始執行
*/
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(traceIdInterceptor).order(0);
}
}
我們使用一下Spring的攔截器功能。在請求開始之前,將請求id新增進MDC。
2.修改日誌的配置檔案
新建一個logback-spring.xml檔案新增如下內容
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%-5level] [%X{traceId}] [%thread] %logger{36} %F.%L %msg%n">
</property>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- 控制檯列印INFO及以上級別的日誌 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<root>
<appender-ref ref="STDOUT"/>
</root>
</configuration>
在日誌的格式LOG_PATTERN中,新增%X{traceId} ,這樣日誌在列印的時候便會去MDC中取出traceid,放在這兒。現在我們可以看看效果。
2019-10-23 11:02:46.649 [INFO ] [/hello - 79ec561a-ef5e-4dc5-91cc-b2143fa3dbd3] [http-nio-8080-exec-1] c.e.demo.controller.TestController TestController.java.18 this is hello 1
2019-10-23 11:02:46.649 [INFO ] [/hello - 79ec561a-ef5e-4dc5-91cc-b2143fa3dbd3] [http-nio-8080-exec-1] c.e.demo.controller.TestController TestController.java.19 this is hello 2
2019-10-23 11:02:46.650 [INFO ] [/hello - 79ec561a-ef5e-4dc5-91cc-b2143fa3dbd3] [http-nio-8080-exec-1] c.e.demo.controller.TestController TestController.java.20 this is hello 3
2019-10-23 11:02:47.612 [INFO ] [/hello - 2ac7450f-40f5-441c-8e10-9b937c006484] [http-nio-8080-exec-2] c.e.demo.controller.TestController TestController.java.18 this is hello 1
2019-10-23 11:02:47.612 [INFO ] [/hello - 2ac7450f-40f5-441c-8e10-9b937c006484] [http-nio-8080-exec-2] c.e.demo.controller.TestController TestController.java.19 this is hello 2
2019-10-23 11:02:47.612 [INFO ] [/hello - 2ac7450f-40f5-441c-8e10-9b937c006484] [http-nio-8080-exec-2] c.e.demo.controller.TestController TestController.java.20 this is hello 3
可以看到,兩個請求通過traceId可以很清楚的區分開了。這樣我們在排查問題的時候,可以通過響應頭裡面的traceId,直接查詢到相關日誌,非常方便。
進階版traceId
之前說過traceId的實現思路是通過ThreadLocal來實現的。使用ThreadLocal有一個前提就是一個請求進來始終是一個執行緒在處理。如果用到spring中的非同步方法,traceId就會失效了。
我們可以做個實驗
//編寫一個service類
public class Service { @Async public void run(){ log.info("this is service run!");
}}//在hello方法中呼叫service的run方法@GetMapping("hello")
public String hello(){ log.info("this is hello 1");
log.info("this is hello 2");
log.info("this is hello 3");
service.run(); return "hello";
}
可以看的列印出來的日誌
2019-10-23 11:12:23.265 [INFO ] [/hello - 96b31833-e8e6-46c5-8459-d423309d1488] [http-nio-8080-exec-1] c.e.demo.controller.TestController TestController.java.22 this is hello 1
2019-10-23 11:12:23.266 [INFO ] [/hello - 96b31833-e8e6-46c5-8459-d423309d1488] [http-nio-8080-exec-1] c.e.demo.controller.TestController TestController.java.23 this is hello 2
2019-10-23 11:12:23.267 [INFO ] [/hello - 96b31833-e8e6-46c5-8459-d423309d1488] [http-nio-8080-exec-1] c.e.demo.controller.TestController TestController.java.24 this is hello 3
2019-10-23 11:12:23.278 [INFO ] [] [task-1] com.example.demo.controller.Service Service.java.17 this is service run!
其實也很簡單,只要對非同步執行緒池跑的物件稍作封裝即可。
@EnableAsync
@Configuration
public class AsyncConfiguration {
@Autowired
private AppConfig config;
@Bean("async")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//設定核心執行緒數量
executor.setCorePoolSize(config.getAsync().getCorePoolSize());
//設定最大執行緒數量
executor.setMaxPoolSize(config.getAsync().getMaxPoolSize());
//設定佇列最大長度
executor.setQueueCapacity(config.getAsync().getQueueCapacity());
//設定執行緒空閒時間
executor.setKeepAliveSeconds(config.getAsync().getKeepLiveSeconds());
//設定執行緒字首
executor.setThreadNamePrefix("async-");
//設定拒絕策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//設定執行緒裝飾器
executor.setTaskDecorator(runnable -> ThreadMdcUtils.wrapAsync(runnable, MDC.getCopyOfContextMap()));
return executor;
}
}
public class ThreadMdcUtils {
public static Runnable wrapAsync(Runnable task, Map<String,String> context){
return () -> {
if(context==null){
MDC.clear();
}else {
MDC.setContextMap(context);
}
if(MDC.get(Constant.TraceId)==null){
MDC.put(Constant.TraceId,UUIDUtils.randomUUID());
}
try {
task.run();
}finally {
MDC.clear();
}
};
}
}
這樣的話,即便呼叫非同步方法,也能獲得統一的日誌id。
相關文章
- 為 fastapi 新增全域性唯一請求id,用於日誌跟蹤ASTAPI
- 只需3分鐘,就能輕鬆建立 一個SpreadJS的React專案JSReact
- ?用 Laravel 開發的一個輕鬆的 Markdown 文件編輯專案Laravel
- Spring MVC 處理一個請求的流程分析SpringMVC
- 輕鬆搞定Retrofit不同網路請求方式的請求引數配置,Retrofit常用註解的使用
- 輕鬆搞定Retrofit不同網路請求方式的請求引數配置,及常用註解使用
- webpack專案輕鬆混用css moduleWebCSS
- 怎樣在Excel中新增水印?學會這個方法可以輕鬆搞定Excel
- laravel 前端有?id=12這麼一個api請求,請教路由怎麼寫Laravel前端API路由
- 統一為專案中的Activity新增Toolbar
- 實時監控.NET Core請求次數:建立記錄最近5分鐘的請求,輕鬆可靠
- 輕鬆理解 Spring AOPSpring
- 輕鬆搞定專案流程自動化
- 飛項的5種應用方法,幫助你輕鬆學會專案管理!專案管理
- Python網路請求庫Requests,媽媽再也不會擔心我的網路請求了(一)Python
- 一行程式碼讓你的專案輕鬆使用Dapr行程
- Java專案Docker化改造(一)JavaDocker
- Spring Boot自動配置原理懂後輕鬆寫一個自己的starterSpring Boot
- 不會Python爬蟲?教你一個通用爬蟲思路輕鬆爬取網頁資料Python爬蟲網頁
- oracle ebs 根據請求id找到對應trace 檔案Oracle
- 專案管理基本流程介紹,讓你輕鬆管理專案專案管理
- 一個使用示例,五個操作步驟!從此輕鬆掌握專案中工作流的開發
- 精盡Spring MVC原始碼分析 - 一個請求的旅行過程SpringMVC原始碼
- [Flutter翻譯]每個專案都有獨立的Flutter版本 - Flutter版本管理器Flutter
- 請求OpenFeign的GET請求時,請求為何失敗?
- [譯] 輕鬆管理 Swift 專案中的不同環境Swift
- 我開發的開源專案,讓.NET7中的EFCore更輕鬆地使用強型別Id型別
- 實戰 | 使用maven 輕鬆重構專案Maven
- 為什麼程式設計是獨一無二的職業?程式設計
- 我用這些開源專案輕鬆搭建了一個線上文件平臺
- 請求GTSS 程式為每個GT 寫入commit log 並且等待其成功返回MIT
- 獨一無二的出現次數
- 記一次vue+element+echarts專案的優化(如何輕鬆將專案效能提升70%)VueEcharts優化
- 犯蠢日記(二)本地環境,A 專案通過 guzzle 請求 B 專案某個介面時,返回 404 響應
- 驚喜!一個檔案多個【請求類】的另類寫法
- 運用MVP框架寫一個完整的請求(RegisterActivity為例)MVP框架
- 瞭解這個專案進度跟蹤管理工具,輕鬆掌握專案進度
- 輕鬆學會 React 鉤子:以 useEffect() 為例React