拦截器如何获取@requestbody_分布式系统中如何优雅地追踪日志(原理篇)
分布式系統中日志追蹤需要考慮的幾個點?
我們一一來解答:
比如,上面這個系統,系統入口在A處,A調用B的服務,B里面又起了一個線程B1去訪問D的服務,B本身又去訪問C服務。
我們就可以這么來跟蹤日志:
實現
我們模擬A到B這兩個服務來實現一個日志跟蹤系統。
為了簡單起見,我們使用SpringBoot,它默認使用的日志框架是logback,而且Slf4j提供了一個包裝了InheritableThreadLocal的類叫MDC,我們只要把traceId放在MDC中,打印日志的時候統一打印就可以了,不用顯式地打印traceId。
我們分成三個模塊:
公共包
前置過濾器,用攔截器實現也是一樣的。
從請求頭中獲取traceId,如果不存在就生成一個,并放入MDC中。
@Slf4j @WebFilter("/**") @Component public class TraceFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;// 從請求頭中獲取traceIdString traceId = request.getHeader("traceId");// 不存在就生成一個if (traceId == null || "".equals(traceId)) {traceId = UUID.randomUUID().toString();}// 放入MDC中,本文來源于工從號彤哥讀源碼MDC.put("traceId", traceId);chain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {} }改造線程池,提交任務的時候進行包裝。
public class TraceThreadPoolExecutor extends ThreadPoolExecutor {public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}@Overridepublic void execute(Runnable command) {// 提交者的本地變量Map<string, string> contextMap = MDC.getCopyOfContextMap();super.execute(()->{if (contextMap != null) {// 如果提交者有本地變量,任務執行之前放入當前任務所在的線程的本地變量中MDC.setContextMap(contextMap);}try {command.run();} finally {// 任務執行完,清除本地變量,以防對后續任務有影響MDC.clear();}});} }改造Spring的異步線程池,包裝提交的任務。
@Slf4j @Component public class TraceAsyncConfigurer implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(8);executor.setMaxPoolSize(16);executor.setQueueCapacity(100);executor.setThreadNamePrefix("async-pool-");executor.setTaskDecorator(new MdcTaskDecorator());executor.setWaitForTasksToCompleteOnShutdown(true);executor.initialize();return executor;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (throwable, method, params) -> log.error("asyc execute error, method={}, params={}", method.getName(), Arrays.toString(params));}public static class MdcTaskDecorator implements TaskDecorator {@Overridepublic Runnable decorate(Runnable runnable) {Map<string, string> contextMap = MDC.getCopyOfContextMap();return () -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {runnable.run();} finally {MDC.clear();}};}}}封裝Http工具類,把traceId加入頭中,帶到下一個服務。
@Slf4j public class HttpUtils {public static String get(String url) throws URISyntaxException {RestTemplate restTemplate = new RestTemplate();MultiValueMap<string, string> headers = new HttpHeaders();headers.add("traceId", MDC.get("traceId"));URI uri = new URI(url);RequestEntity<!--?--> requestEntity = new RequestEntity<>(headers, HttpMethod.GET, uri);ResponseEntity<string> exchange = restTemplate.exchange(requestEntity, String.class);if (exchange.getStatusCode().equals(HttpStatus.OK)) {log.info("send http request success");}return exchange.getBody();}}A服務
A服務通過Http調用B服務。
@Slf4j @RestController public class AController {@RequestMapping("a")public String a(String name) {log.info("Hello, " + name);try {// A中調用Breturn HttpUtils.get("http://localhost:8002/b");} catch (Exception e) {log.error("call b error", e);}return "fail";} }A服務的日志輸出格式:
中間加了[%X{traceId}]一串表示輸出traceId。
# 本文來源于工從號彤哥讀源碼 logging:pattern:console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx'B服務
B服務內部有兩種跨線程調用:
- 利用Spring的異步線程池
- 使用自己的線程池
BController.java
@Slf4j @RestController public class BController {@Autowiredprivate BService bService;@RequestMapping("b")public String b() {log.info("Hello, b receive request from a");bService.sendMsgBySpring();bService.sendMsgByThreadPool();return "ok";} }BService.java
@Slf4j @Service public class BService {public static final TraceThreadPoolExecutor threadPool = new TraceThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));@Asyncpublic void sendMsgBySpring() {log.info("send msg by spring success");}public void sendMsgByThreadPool() {threadPool.execute(()->log.info("send msg by thread pool success"));} }B服務的日志輸出格式:
中間加了[%X{traceId}]一串表示輸出traceId。
logging:pattern:console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx'測試
打開瀏覽器,輸入http://localhost:8001/a?name=andy。
A服務輸出日志:
2019-12-26 21:36:29.132 INFO 5132 --- [nio-8001-exec-2] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.a.AController : Hello, andy 2019-12-26 21:36:35.380 INFO 5132 --- [nio-8001-exec-2] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.common.HttpUtils : send http request successB服務輸出日志:
2019-12-26 21:36:29.244 INFO 2368 --- [nio-8002-exec-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.b.BController : Hello, b receive request from a 2019-12-26 21:36:29.247 INFO 2368 --- [nio-8002-exec-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 2019-12-26 21:36:35.279 INFO 2368 --- [ async-pool-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.b.BService : send msg by spring success 2019-12-26 21:36:35.283 INFO 2368 --- [pool-1-thread-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.b.BService : send msg by thread pool success可以看到,A服務成功生成了traceId,并且傳遞給了B服務,且B服務線程間可以保證同一個請求的traceId是可以傳遞的。
文章來源:https://my.oschina.net/u/4108008/blog/3152201關注我了解更多程序員資訊技術,領取豐富架構資料。
總結
以上是生活随笔為你收集整理的拦截器如何获取@requestbody_分布式系统中如何优雅地追踪日志(原理篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 根据概率抽奖算法
- 下一篇: windows路由表 重启后就还原了_绕