MDC介绍:MDC(MappedDiagnosticContext)是log4j、logback和log4j2提供的一个功能,方便在多线程情况下进行日志记录。MDC可以看作是一个绑定当前线程的哈希表,可以在其中添加键值对。MDC中包含的内容可以由在同一线程中执行的代码访问。当前线程的子线程会继承其父线程中MDC的内容。当需要日志记录时,仅从MDC获取所需的信息。MDC的内容由程序在适当的时候保存。对于一个web应用,这些数据通常保存在请求处理的最开始API描述:clear()=>removeallMDCget(Stringkey)=>获取当前线程中指定key的值MDCgetContext()=>获取当前线程的MDCMDCput(Stringkey,Objecto)=>将指定的键值对存入当前线程的MDCremove(Stringkey)=>删除指定的键值对在当前线程的MDC优点:代码简洁,日志风格统一,打印日志时无需手动拼写traceId,即LOGGER.info("traceId:{}",traceId)即可暂时只能想到这里。MDC使用添加拦截器publicclassLogInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//如果有上层调用,使用上层IDStringtraceId=request.getHeader(常量.TRACE_ID);如果(traceId==null){traceId=TraceIdUtil.getTraceId();}MDC.put(Constants.TRACE_ID,traceId);返回真;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{//删除MDC.remove(Constants.TRACE_ID);}}修改日志格式[TRACEID:%X{traceId}]%d{HH:mm:ss.SSS}%-5level%class{-1}.%M()/%L-%msg%xEx%nkey为%X{traceId},traceId与MDC中的key名称一致。它是如此易于使用,但在某些情况下,traceId将无法获取图片。MDC中的问题。子线程中打印日志丢失。traceId在HTTP调用中丢失........丢失traceId的情况,一个接一个解决,千万不要提前优化解决MDC子线程日志打印丢失traceId的问题子线程会丢失traceIdin打印日志的过程,解决方法是重写线程池,对于直接new线程创建的情况不考虑【实际应用中应该避免这种用法】。重写线程池无非就是把任务封装一次。,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue){super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue);}publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory){super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory);}publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,RejectedExecutionHandlerhandler){super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,handler);}publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler){super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);}@Overridepublicvoidexecute(Runnabletask){super.execute(ThreadMdcUtil.wrap(task,MDC.getCopy))OfContext(}@OverridepublicFuturesubmit(Runnabletask,Tresult){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()),结果);}@OverridepublicFuturesubmit(Callabletask){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));}@OverridepublicFuture>submit(Runnabletask){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));}}说明:继承ThreadPoolExecutor类,并通过ThreadMdcUtil重新执行任务,将任务包装一次ThreadtraceId封装工具类:ThreadMdcUtil.javapublicclassThreadMdcUtil{publicstaticvoidsetTraceIdIfAbsent(){if(MDC.get(Constants.TRACE_ID)==null){MDC.put(Constants.TRACE_ID,TraceIdUtil.getTraceId());}}publicstaticCallablewrap(finalCallablecallable,finalMapcontext){return()->{if(context==null){MDC.clear();}else{MDC.setContextMap(上下文);}setTraceIdIfAbsent();尝试{返回callable.call();}最后{MDC.clear();}};}publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){return()->{if(context==null){MDC.clear();}else{MDC.setContextMap(上下文);}setTraceIdIfAbsent();试试{runnable.run();}最后{MDC.clear();}};}}说明【以封装Runnable为例】:判断当前线程的MDC对应的Map是否存在,如果存在则设置MDC中的traceId值,如果不存在则新建产生。对于不是子线程的情况,如果是子线程,MDC中的traceId不为null。执行run方法的代码相当于下面这样写,这样会更直观if(context==null){MDC.clear();}else{MDC.setContextMap(上下文);}setTraceIdIfAbsent();试试{runnable.run();}最后{MDC.clear();}}};返回的是包装好的Runnable。任务执行前[runnable.run()],主线程将Map设置为当前线程[即MDC.setContextMap(context)],使得子线程和主线程对应的MapMDC是这是相同的。判断当前线程的MDC对应的Map是否存在。如果存在,则在MDC中设置traceId值。如果不存在,则重新生成。对于不是子线程的情况,如果是子线程,MDC中的traceId不为null。执行run方法HTTP调用丢失traceId使用HTTP调用第三方服务接口时,traceId会丢失。需要修改http调用工具,发送时在requestheader中添加traceId,下层callee添加拦截器获取header中的traceIdMDC中HTTP调用的方式有很多种,比较常见的有HttpClient、OKHttp、RestTemplate,所以只给出这几种HTTP调用。HttpRequesthttpRequest,HttpContexthttpContext)抛出HttpException,IOException{StringtraceId=MDC.get(Constants.TRACE_ID);//如果当前线程调用中有traceId,则透传traceIdif(traceId!=null){//AddRequestbodyhttpRequest.addHeader(Constants.TRACE_ID,traceId);}}}实现HttpRequestInterceptor接口,重写process方法。如果调用线程中包含traceId,则需要将获取的traceId通过请求中的header透明的向下传递给HttpClient添加拦截器privatestaticCloseableHttpClienthttpClient=HttpClientBuilder.create().addInterceptorFirst(新的HttpClientTraceIdInterceptor()).build();通过addInterceptorFirst方法为HttpClient添加拦截器OKHttp:请求请求=空;if(traceId!=null){//添加请求体request=chain.request().newBuilder().addHeader(Constants.TRACE_ID,traceId).build();}响应originResponse=chain.proceed(request);返回原点响应;}}实现Interceptor拦截器,重写拦截器方法。实现逻辑与HttpClient类似。如果可以获取到当前线程的traceId,则透传给OkHttp添加拦截调用addNetworkInterceptor方法添加拦截;RestTemplate:实现RestTemplate拦截器publicclassRestTemplateTraceIdInterceptor实现ClientHttpRequestInterceptor{@OverridepublicClientHttpResponseintercept(HttpRequesthttpRequest,byte[]bytes,ClientHttpRequestExecutionclientHttpRequestExecution)throwsIOException{StringtraceId=MDC.get(Constants.TRACE_ID);如果(traceId!=null){httpRequest.getHeaders()。添加(常量.TRACE_ID,traceId);}返回clientHttpRequestExecution.execute(httpRequest,bytes);}}实现ClientHttpRequestInterceptor接口,并重写拦截方法,其余逻辑相同,不再重复说明给RestTemplate添加拦截器restTemplate.setInterceptors(Arrays.asList(newRestTemplateTraceIdInterceptor()));调用setInterceptors方法添加拦截器第三方服务拦截器:HTTP调用第三方服务接口全流程traceId需要第三方服务配合,第三方服务需要添加拦截器在请求头中获取traceId并添加到MDC公共类LogInterceptor实现HandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest请求,HttpServletResponse响应,对象处理程序)抛出异常{//如果有上层调用,则使用上层IDStringtraceId=request.getHeader(Constants.TRACE_ID);如果(traceId==null){traceId=TraceIdUtils.getTraceId();}MDC.put("traceId",traceId);返回真;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{MDC.删除(常量.TRACE_ID);}}解释:先从请求头中获取traceId。如果从请求头中取不到traceId,说明不是第三方调用。直接生成新的traceId。将生成的traceId存储在MDC中除了添加拦截器之外,还需要添加日志格式打印的traceId,如下:[TRACEID:%X{traceId}]%d{HH:mm:ss.SSS}%-5level%class{-1}.%M()/%L-%msg%xEx%n需要添加%X{traceId}