Spring Boot + MDC 实现全链路调用日志跟踪,这才叫优雅!
阅读本文大概需要 5.5 分钟。
clear() => 移除所有MDC
get (String key) => 获取当前线程MDC中指定key的值
getContext() => 获取当前线程MDC的MDC
put(String key, Object o) => 往当前线程的MDC中存入指定的键值对
remove(String key) => 删除当前线程MDC中指定的键值对
public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上层调用就用上层的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 {//调用结束后删除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调用丢失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类,重新执行任务的方法
通过ThreadMdcUtil对任务进行一次包装
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();}};}}
判断当前线程对应MDC的Map是否存在,存在则设置
设置MDC中的traceId值,不存在则新生成,针对不是子线程的情况,如果是子线程,MDC中traceId不为null
执行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);//当前线程调用中有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 {//如果有上层调用就用上层的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则说明不是第三方调用,直接生成一个新的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) 有什么区别?资深开发竟然都理解错了!
内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper、数据结构、限流熔断降级......等技术栈!
⬇戳阅读原文领取! 朕已阅 
评论

