Spring Boot + MDC 實現(xiàn)全鏈路調(diào)用日志跟蹤,這才叫優(yōu)雅!
閱讀本文大概需要 5.5?分鐘。
clear() => 移除所有MDC
get (String key) => 獲取當(dāng)前線程MDC中指定key的值
getContext() => 獲取當(dāng)前線程MDC的MDC
put(String key, Object o) => 往當(dāng)前線程的MDC中存入指定的鍵值對
remove(String key) => 刪除當(dāng)前線程MDC中指定的鍵值對
public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上層調(diào)用就用上層的IDString traceId = request.getHeader(Constants.TRACE_ID);if (traceId == null) {traceId = TraceIdUtil.getTraceId();}MDC.put(Constants.TRACE_ID, traceId);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {//調(diào)用結(jié)束后刪除MDC.remove(Constants.TRACE_ID);}}
"pattern" >[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
子線程中打印日志丟失traceId
HTTP調(diào)用丟失traceId
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor { public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);????} public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);????} public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } public void execute(Runnable task) { super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } public Future submit(Runnable task, T result) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result); } public Future submit(Callable task) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } public Future> submit(Runnable task) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); }} 繼承ThreadPoolExecutor類,重新執(zhí)行任務(wù)的方法
通過ThreadMdcUtil對任務(wù)進行一次包裝
public class ThreadMdcUtil {public static void setTraceIdIfAbsent() {if (MDC.get(Constants.TRACE_ID) == null) {MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());}????}public staticCallable wrap(final Callable {callable, final Map context) return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {return callable.call();} finally {MDC.clear();}};????}public static Runnable wrap(final Runnable runnable, final Mapcontext) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}};}}
判斷當(dāng)前線程對應(yīng)MDC的Map是否存在,存在則設(shè)置
設(shè)置MDC中的traceId值,不存在則新生成,針對不是子線程的情況,如果是子線程,MDC中traceId不為null
執(zhí)行run方法
public static Runnable wrap(final Runnable runnable, final Mapcontext) {return new Runnable() {public void run() {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}}};}
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {String traceId = MDC.get(Constants.TRACE_ID);//當(dāng)前線程調(diào)用中有traceId,則將該traceId進行透傳if (traceId != null) {//添加請求體httpRequest.addHeader(Constants.TRACE_ID, traceId);}}}
private static CloseableHttpClient httpClient = HttpClientBuilder.create().addInterceptorFirst(new HttpClientTraceIdInterceptor()).build();
public class OkHttpTraceIdInterceptor implements Interceptor {public Response intercept(Chain chain) throws IOException {String traceId = MDC.get(Constants.TRACE_ID);Request request = null;if (traceId != null) {//添加請求體request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build();}????????Response?originResponse?=?chain.proceed(request);return originResponse;}}
private static OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new OkHttpTraceIdInterceptor()).build();
public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {String traceId = MDC.get(Constants.TRACE_ID);if (traceId != null) {httpRequest.getHeaders().add(Constants.TRACE_ID, traceId);}return clientHttpRequestExecution.execute(httpRequest, bytes);}}
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));
public class LogInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上層調(diào)用就用上層的IDString traceId = request.getHeader(Constants.TRACE_ID);if (traceId == null) {traceId = TraceIdUtils.getTraceId();}MDC.put("traceId", traceId);return true;????}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {????}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {MDC.remove(Constants.TRACE_ID);}}
先從request header中獲取traceId
從request header中獲取不到traceId則說明不是第三方調(diào)用,直接生成一個新的traceId
將生成的traceId存入MDC中
"pattern" >[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
需要添加%X{traceId}
推薦閱讀:
int(1) 和 int(10) 有什么區(qū)別?資深開發(fā)竟然都理解錯了!
內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper、數(shù)據(jù)結(jié)構(gòu)、限流熔斷降級......等技術(shù)棧!
?戳閱讀原文領(lǐng)取!? ? ? ? ? ? ? ??? ??? ? ? ? ? ? ? ? ? ?朕已閱?

