当前位置: 首页 > 科技观察

JavaWeb日志跟踪的简单实现

时间:2023-03-22 10:33:20 科技观察

一、前言在编码过程中,经常需要编写打印日志语句。我们期望的是,同一个业务的日志都在一块。当出现问题时,最好使用日志来排查问题。.现实情况是,在应用运行过程中,日志的输出往往来自于不同的线程,甚至在不同的微服务中,各种日志记录往往相互穿插,很难串联起来。因此,经常会在日志中手动添加一些关键字来跟踪接口的调用链接。但是,这种手动添加关键字或唯一标识的方式,在微服务场景下,上下游应用开发者的编码风格难以形成统一规范,手工编写也难以称得上优雅。2、MDC简介MDC(MappedDiagnosticContext)是log4j、logback和log4j2提供的一个功能,方便在多线程情况下进行日志记录。MDC可以看做是一个绑定到当前线程的哈希表,MDC中包含的内容可以被同一线程中执行的代码访问到。MDC中的键值对可以直接被日志框架使用(即“打印”),只需要配置相应的日志模式即可。例如模式如下:%d{HH:mm:ss.SSS}[%thread][%X{TraceId}]%-5level%logger{50}-%msg%n代码如下:公共类MDCTest{privatestaticfinalLoggerlog=LoggerFactory.getLogger(MDCTest.class);@Testvoidtest(){MDC.put("TraceId","123456789");log.info("你好{}","世界");}}此时控制台会输出:21:16:04.342[main][123456789]INFOcom.nk.MDCTest-helloworld三、实现方案1、基本思路修改logpattern,将traceid放入MDC业务启动时,去掉服务结束时MDC的traceid。这样做的好处是代码简洁,不需要手动写traceid,日志风格可以保持统一。业务启动的时机一般是在应用收到HTTP请求时,可以使用Filter或者SpringMVC的Interceptor来初始化清除MDC中的traceid。Dubbo调用时,也可以通过类似功能的filter对MDC中的traceid进行操作,从而实现traceid传递的功能。2.实现(以SpringBoot为例)2.1修改日志模式在SpringBoot中,直接修改application.properties即可:logging.pattern.console=%d{yyyy-MM-ddHH:mm:ss.SSS}[%thread][%X{TraceId}]%-5level%logger{50}-%msg%n重点是%X{TraceId},其中TraceId需要作为key出现在MDC中。2.1.2业务启动的TraceId工具类封装了MDC对traceid的基本操作:publicfinalclassTraceIdUtil{privatestaticfinalStringTRACE_ID_KEY="TraceId";privateTraceIdUtil(){}publicstaticvoidputIfAbsent(){if(StrUtil.isBlank(get())){put(UUID.randomUUID().toString());}}publicstaticvoidremove(){if(get()!=null){MDC.remove(TRACE_ID_KEY);}}publicstaticStringget(){返回MDC.get(TRACE_ID_KEY);}publicstaticvoidput(StringtraceId){MDC.put(TRACE_ID_KEY,traceId);}}Filtermethod和Interceptor可以选择,基本思路是一样的。Filter方式@ComponentpublicclassLogFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{TraceIdUtil.putIfAbsent();//生成traceid放入MDC中try{filterChain.doFilter(servletRequest,servletResponse);}finally{TraceIdUtil.remove();//移去MDC中的traceid}}}Interceptor@ConfigurationpublicclassLogInterceptorimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newAsyncHandlerInterceptor(){@OverridepublicbooleanpreHandle(HttpServletRequest请求,HttpServletResponse响应,对象处理程序)抛出异常{TraceIdUtil.putIfAbsent();//生成traceid放入MDCreturnAsyncHandlerInterceptor.super.preHandle(request,response,handler);}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,ObjectExceptionhandler,Exceptionsexceptions){TraceIdUtil.remove();//移除MDC中的traceidAsyncHandlerInterceptor.super.afterCompletion(request,response,handler,ex);}});WebMvcConfigurer.super.addInterceptors(注册表);}}2.1.2Business正常使用logger,无需关心traceid例如:@RestController@RequestMapping("/api/user")@Slf4jpublicclassUserController{@AutowiredprivateUserServiceuserService;@GetMapping("/{userId}")publicUserDtoqueryUser(@PathVariableLonguserId){log.info("通过id查询用户:{}",userId);UserDtouser=userService.query(userId);log.info("查询用户结果:{}",user);返回用户;}}请求该接口会输出如下Log样式:2022-04-0509:40:17.638[http-nio-8080-exec-1][a02b13d81c224e49956afd4efbb85ca8]INFOcom.nk.webapp.controller.UserController-readytoqueryuserbyid:12022-04-0509:40:17.670[http-nio-8080-exec-1][a02b13d81c224e49956afd4efbb85ca8]INFOcom.nk.webapp.controller.UserController-查询结果:UserDto(userId=1,username=zhang3,age=23,email=abc@example.com)4.总结日志链接跟踪的核心是使用MDC作为traceid的载体。业务开始时,traceid一般由拦截器生成并放入MDC,根据MDC的相关特性设置traceid。投射到日志文本中,使同一业务调用链接中的日志具有唯一标识。

最新推荐
猜你喜欢