当前位置: 首页 > 后端技术 > Java

SpringBoot+MDC实现全链路调用日志跟踪,堪称优雅,.

时间:2023-04-01 14:36:42 Java

作者:何甜甜在哪里?\来源:juejin.cn/post/6844904101483020295之前有一篇文章简单介绍了MDC。本次,我们将结合生产中的具体案例和具体问题,深入了解MDC。MDC简介1、简介:MDC(MappedDiagnosticContext)是log4j、logback和log4j2提供的一个功能,方便在多线程情况下进行日志记录。MDC可以看作是一个绑定当前线程的哈希表,可以在其中添加键值对。MDC中包含的内容可以由在同一线程中执行的代码访问。当前线程的子线程会继承其父线程中MDC的内容。当需要日志记录时,仅从MDC获取所需的信息。MDC的内容由程序在适当的时候保存。对于Web应用程序,此数据通常在处理请求的最开始时保存。2、API说明:clear():清除所有MDCget(Stringkey):获取当前线程的MDC中指定key的值getContext():获取当前线程的MDC的MDCput(Stringkey,Objecto):发送当前线程的MDCStorethespecifiedkey-valuepairinremove(Stringkey):删除当前线程MDC中的指定键值对3.优点:代码简洁,日志风格是统一的,不需要在打印日志时手动拼写traceId,即LOGGER.info("traceId:{}",traceId)。MDC使用1.添加拦截器publicclassLogInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//如果有上层调用,使用上层IDStringtraceId=request.getConheader(.TRACE_ID);如果(traceId==null){traceId=TraceIdUtil.getTraceId();}MDC.put(Constants.TRACE_ID,traceId);返回真;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponder,Objecthandler,ModelAndViewmodelAndView)throwsException{}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{//调用后删除MDC.删除(常量.TRACE_ID);}}2。格式[TRACEID:%X{traceId}]%d{HH:mm:ss.SSS}%-5level%class{-1}.%M()/%L-%msg%xEx%n重点是%X{traceId},traceId和MDC中的key名称一致,好用,但是有些情况下,traceId将无法获取到MDC存在的问题子线程中打印日志丢失traceId,HTTP调用丢失traceId,traceId丢失,一个一个,一个一个,并且没有提前优化。解决MDC子线程日志打印丢失traceId问题子线程traceId在打印日志的过程中会丢失,解决方法是重写线程池,不考虑直接新建线程的情况【这个用法应该实际应用中应该避免]],重写线程池无非是把任务封装一次。线程池封装类:ThreadPoolExecutorMdcWrapper.javapublicclassThreadPoolExecutorMdcWrapperextendsThreadPoolExecutor{publicThreadPoolExecutorMdcWrapper(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkAQueue){super(corePoolSize,workAqueue);}}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.getCopyOfContextMap()));}@OverridepublicFuturesubmit(Runnabletask,Tresult){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()),result);}@OverridepublicFuturesubmit(Callabletask){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));}@OverridepublicFuturesubmit(Runnabletask){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));}}说明:继承ThreadPoolExecutor类,通过ThreadMdcUtil重新执行一次任务封装线程traceId封装工具类: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,finalMap<String,String>context){return()->{if(context==null){MDC.clear();}else{MDC.setContextMap(上下文);}setTraceIdIfAbsent();试试{runnable.run();}最后{MDC.clear();}};}}解释【以封装Runnable为例】:判断当前线程的MDC对应的Map是否存在,存在则设置MDC中的traceId值,不存在则生成新的。对于不是子线程的情况,如果是子线程,MDC中的traceId不为null执行run方法代码相当于下面这样写,会更直观publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){returnnewRunnable(){@Overridepublicvoidrun(){if(context==null){MDC.clear();}}else{MDC.setContextMap(上下文);}setTraceIdIfAbsent();试试{runnable.run();}最后{MDC.clear();}}};}返回的是打包好的Runnable。任务执行前【runnable.run()】首先设置主线程的Map为当前线程【MDC.setContextMap(context)】这样子线程和主线程MDC对应的Map为相同。HTTP调用丢失。使用HTTP调用第三方服务接口时,traceId会丢失。需要修改http调用工具,发送时在请求头中加入traceId。给下层callee添加拦截器,获取header中的traceId,添加到MDC中。HTTP调用的方式有很多种,比较常见的有HttpClient、OKHttp、RestTemplate,所以这里只给出这几种HTTP调用的解决方案。1.HttpClient:implementHttpClientinterceptor:publicclassHttpClientTraceIdInterceptorimplementsHttpRequestInterceptor{@Overridepublicvoidprocess(HttpRequesthttpRequest,HttpContexthttpContext)throwsHttpException,IOException{stStringtraceId=MDC.get在当前线程调用中)(如果有traceId,traceId会被透传if(traceId!=null){//添加请求体httpRequest.addHeader(Constants.TRACE_ID,traceId);}}}如果调用线程中包含traceId,实现HttpRequestInterceptor接口,重写process方法,需要将获取到的traceId通过请求中的header透传下去。为HttpClient添加拦截器:privatestaticCloseableHttpClienthttpClient=HttpClientBuilder.create().addInterceptorFirst(newHttpClientTraceIdInterceptor()).build();通过addInterceptorFirst方法为HttpClient添加拦截器。2.OKHttp:实现OKHttp拦截器:请求请求=空;if(traceId!=null){//添加请求体request=chain.request().newBuilder().addHeader(Constants.TRACE_ID,traceId).build();}响应originResponse=chain.proceed(request);返回原点响应;}}实现Interceptor拦截器,重写拦截器方法,实现逻辑类似HttpClient,如果能获取到当前线程的traceId,则向下透传。为OkHttp添加拦截器:privatestaticOkHttpClientclient=newOkHttpClient.Builder().addNetworkInterceptor(newOkHttpTraceIdInterceptor()).build();调用addNetworkInterceptor方法添加拦截器。3、RestTemplate:实现RestTemplate拦截器:publicclassRestTemplateTraceIdInterceptorimplementsClientHttpRequestInterceptor{@OverridepublicClientHttpResponseintercept(HttpRequesthttpRequest,byte[]bytes,ClientHttpRequestExecutionclientHttpRequestExecution)throwsIOException{StringtraceId=MDC.get(CEIDConstants);if(traceId!=null){httpRequest.getHeaders().add(Constants.TRACE_ID,traceId);}返回clientHttpRequestExecution.execute(httpRequest,bytes);}}实现ClientHttpRequestInterceptor接口,重写拦截方法。其余逻辑相同。重复说明。为RestTemplate添加拦截器:restTemplate.setInterceptors(Arrays.asList(newRestTemplateTraceIdInterceptor()));调用setInterceptors方法添加拦截器。4、第三方服务拦截器:http调用第三方服务接口的整个过程中的traceId需要第三方服务的配合。第三方服务需要添加拦截器,获取请求头中的traceId,添加到MDC中。publicclassLogInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{//如果有上层调用,使用上层IDStringtraceId=request.getHeader(ConstantsIDtraceId);TRAif==null){traceId=TraceIdUtils.getTraceId();}MDC.put("traceId",traceId);返回真;}@OverridepublicvoidpostHandle(HttpServletRequest请求,HttpServletResponse响应,对象处理程序,ModelAndViewmodelAndView)抛出Ex}@OverridepublicvoidafterCompletion(HttpServletRequest请求,HttpServletResponse响应,对象处理程序,异常ex)抛出异常{);表示不是第三方调用,直接生成新的traceId,并将生成的traceId存放在MDC中。除了添加拦截器外,还需要添加日志格式的traceId打印,如下:%msg%xEx%n注意:需要添加%X{traceId}。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。.3.SpringBoot2.x教程,太全面了!4.20w程序员红包封面,快拿。..5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!

最新推荐
猜你喜欢