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

SpringBoot在不引入组件的情况下实现日志链接跟踪,让日志定位更方便!

时间:2023-04-02 01:39:50 Java

来源:blog.csdn.net/qq_35387940/article/details/125062368前言从文章的标题就可以看出这篇文章的内容。这是我一个朋友的反馈:好像是的,确实这种现象很普遍。有的时候一个业务调用链场景很长,调整各种方法。看日志的时候,各种接口的日志穿插其中,真是一头雾水。模糊匹配搜索日志能否解决?能解决一点。但是,无法完整呈现整个链接相关的日志。为了方便,显然,我们需要的是将同一个业务调用链上的日志串起来。什么效果?先来看一下实现后的效果图:执行完后会发现模糊匹配的日志,效果不会和刚才一样。cat-ninfo.log|grep"a415ad50dbf84e99b1b56a31aacd209c"或grep-10'a415ad50dbf84e99b1b56a31aacd209c'info.log(10指上下10行)。文字约定,先看看本次实战最终项目的结构:SpringBoot基础知识就不介绍了,推荐观看这个免费教程:https://github.com/javastacks/spring-boot-最佳实践①pom.xml依赖于org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-test测试org.springframework.bootspring-boot-starter-loggingorg.projectlomboklombok1.16.10②集成logback,printlogs,logback-spring.xml(简单配置下)[%X{TRACE_ID}]%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n${log}/%d{yyyy-MM-dd}.log30[%X{TRACE_ID}]%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread]%-5level%logger{50}-%msg%n10MBapplication.ymlserver:port:8826logging:config:classpath:logback-spring.xml③自定义日志拦截器LogInterceptor.java的用途:每个链接,线程维度,添加最终链接IDTRACE_ID导入org.slf4j.MDC;导入org.springframework.lang.Nullable;导入org.springframework.util.StringUtils;导入org.springframework.web.servlet.HandlerInterceptor;导入javax.servlet.http.HttpServletRequest;导入javax.servlet。http.HttpServletResponse;导入java.util.UUID;/***@Author:JCccc*@Date:2022-5-3010:45*@Description:*/publicclassLogInterceptorimplementsHandlerInterceptor{privatestaticfinalStringTRACE_ID="TRACE_ID";@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){Stringtid=UUID.randomUUID().toString().replace("-","");//考虑让客户端传入IncominglinkID,但是需要保证一定的复杂性和唯一性;如果不使用默认的UUID,会自动生成}MDC.put(TRACE_ID,tid);返回真;}@OverridepublicvoidafterCom完成(HttpServletRequest请求,HttpServletResponse响应,对象处理程序,@NullableExceptionex){MDC.remove(TRACE_ID);}}MDC(MappedDiagnosticContext)诊断上下文映射是@Slf4j提供的一个工具,支持动态打印日志信息WebConfigurerAdapter.java添加拦截器servlet.config.annotation.WebMvcConfigurer;/***@Author:JCccc*@Date:2022-5-3010:47*@Description:*/@ConfigurationpublicclassWebConfigurerAdapterimplementsWebMvcConfigurer{@BeanpublicLogInterceptorlogInterceptor(){returnnewLogInterceptor();}}@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(logInterceptor());//可以指定哪些需要拦截,哪些不应该拦截。其实也可以使用自定义注解来更灵活的完成//.addPathPatterns("/**")//.excludePathPatterns("/testxx.html");}}ps:其实拦截部分使用自定义注解+aop也很灵活。这时候,其实就完成了,就这么简单。下面写个测试接口看看效果:@PostMapping("doTest")publicStringdoTest(@RequestParam("name")Stringname)throwsInterruptedException{log.info("Inputparametername={}",name);测试跟踪();log.info("调用结束名称={}",name);return"Hello,"+name;}privatevoidtestTrace(){log.info("这是一行信息日志");日志。error("这是一行错误日志");testTrace2();}privatevoidtestTrace2(){log.info("这也是一行infolog");}效果(OK):还没完成。接下来我们看一个使用子线程的场景:特意写一个异步线程加入到这个调用中:再次执行看效果,显然是子线程丢失了trackId:所以我们需要根据子线程的使用,思路:将父线程的trackId传给子线程即可。①ThreadPoolConfig.java定义线程池,交给spring管理.concurrent.Executor;/***@Author:JCccc*@Date:2022-5-3011:07*@Description:*/@Configuration@EnableAsyncpublicclassThreadPoolConfig{/***声明一个线程池**@returnexecutor*/@Bean("MyExecutor")publicExecutorasyncExecutor(){MyThreadPoolTask??Executorexecutor=newMyThreadPoolTask??Executor();//核心线程数为5:创建线程池时初始化的线程数executor.setCorePoolSize(5);//最大线程数5:线程池中的最大线程数。只有当缓冲队列满了才会线程executor.setMaxPoolSize(5);//bufferqueue500:用于缓冲执行任务的队列executor.setQueueCapacity(500);//允许线程空闲时间为60秒:空闲时间到后,除核心线程外的线程将被销毁executor.setKeepAliveSeconds(60);//线程池名称的前缀:设置后方便我们定位线程池executor.setThreadNamePrefix("asyncJCccc");executor.initialize();returnexecutor;}}②MyThreadPoolTask??Executor.java是我们自己写的,重写了一些方法:importorg.slf4j.MDC;importorg.springframework.scheduling.concurrent.ThreadPoolTask??Executor;importjava.util.concurrent.Callable;importjava.util.concurrent.Future;/***@Author:JCccc*@Date:2022-5-3011:13*@Description:*/publicfinalclassMyThreadPoolTask??ExecutorextendsThreadPoolTask??Executor{publicMyThreadPoolTask??Executor(){super();}@Overridepublicvoidexecute(Runnabletask){super.execute(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));}@OverridepublicFuture提交(Callable任务){returnsuper.submit(ThreadMdcUtil.wrap(task,MDC.getCopyOfContextMap()));}@OverridepublicFuturesubmit(Runnabletask){returnsuper.submit(ThreadMdcUtil。wrap(任务,MDC.getCopyOfContextMap()));}}③ThreadMdcUtil.javaimportorg.slf4j.MDC;导入java.util.Map;导入java.util.UUID;导入java.util.concurrent.Callable;/***@Author:JCccc*@Date:2022-5-3011:14*@Description:*/publicfinalclassThreadMdcUtil{privatestaticfinalStringTRACE_ID="TRACE_ID";//获取唯一IDpublicstaticStringgenerateTraceId(){returnUUID.randomUUID().toString();}publicstaticvoidsetTraceIdIfAbsent(){if(MDC.get(TRACE_ID)==null){MDC.put(TRACE_ID,generateTraceId());}}/***当父线程向线程池提交任务时,会把自己的MDC中的数据拷贝到子线程中**@paramcallable*@paramcontext*@param*@return*/publicstaticCallablewrap(finalCallablecallable,finalMapcontext){return()->{if(context==null){MDC.清除();}else{MDC。setContextMap(上下文);}setTraceIdIfAbsent();尝试{返回callable.call();}最后{MDC.clear();}};}/***当父线程向线程池提交任务时,将自己MDC中的数据复制到子线程**@paramrunnable*@paramcontext*@return*/publicstaticRunnablewrap(finalRunnablerunnable,finalMapcontext){return()->{if(context==null){MDC.clear();}}else{MDC.setContextMap(上下文);}setTraceIdIfAbsent();试试{runnable.run();}最后{MDC.clear();}};}}OK,重启服务看看看效果:可以看到,子线程的日志也串起来了。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!

最新推荐
猜你喜欢