好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java Web中日志跟踪的简单实现

一、前言

在编码过程中,常常需要写打印日志语句,我们期望的是同一个业务的日志都在一块,在出问题的时候好根据日志来排查问题。而现实是在应用运行中,日志的输出常常来自不同线程,甚至是在不同微服务中,各种日志记录往往彼此穿插,很难串起来。所以往往在日志中手动增加一些关键字,来对接口的调用链路来进行跟踪。但这种手动增加关键字或唯一标识的做法在微服务场景下,很难在上下游应用的开发人员的编码风格形成统一的规范,并且手动编写也很难称得上优雅。

二、MDC介绍

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,MDC 中包含的内容可以被同一线程中执行的代码所访问。

MDC中的键值对是可以直接被日志框架所使用(即[打印])的,只需要配置相应日志pattern。例如pattern如下:

 % d { HH : mm : ss .SSS  }   [  % thread ]   [  % X { TraceId }  ]   %-  5 level  % logger {  50  }   -   % msg % n

代码如下:

public class MDCTest  {  private static final Logger log  =  LoggerFactory .getLogger  ( MDCTest .class  )  ;  @Test
    void test (  )   {  MDC .put  (  "TraceId"  ,   "123456789"  )  ;  log .info  (  "hello {}"  ,   "world"  )  ;   }   } 

此时控制台将输出:

 21  :  16  :  04.342   [ main ]   [  123456789  ]  INFO  com .nk  .MDCTest   -  hello world

三、实现方案

1、基本思路

修改日志pattern,并在业务开始的时候将trace id放入到MDC,在业务结束时去除MDC的trace id。这样的好处便是代码简洁,不需要手动写trace id,日志风格也能保持统一。

业务开始的时机一般是应用收到HTTP请求,所以可以用Filter或SpringMVC的Interceptor来对MDC中trace id进行初始化和清除。在Dubbo调用的时候也可以通过类似功能的Filter来对MDC中trace id进行操作,从而达到trace id传递的作用。

2、实现(以SpringBoot为例)

2.1 修改log pattern

在SpringBoot中,直接修改application.properties即可:

logging .pattern  .console  =% d { yyyy - MM - dd HH : mm : ss .SSS  }   [  % thread ]   [  % X { TraceId }  ]   %-  5 level  % logger {  50  }   -   % msg % n

重点在于%X{TraceId},其中TraceId需要作为key出现在MDC里。

2.1.1 业务开始

TraceId工具类,封装MDC关于trace id的基础操作:

public final class TraceIdUtil  {  private static final String TRACE_ID_KEY  =   "TraceId"  ;  private TraceIdUtil (  )   {   }  public static void putIfAbsent (  )   {  if  ( StrUtil .isBlank  ( get (  )  )  )   {  put ( UUID .randomUUID  (  )  .toString  (  )  )  ;   }   }  public static void remove (  )   {  if  ( get (  )   !=   null  )   {  MDC .remove  ( TRACE_ID_KEY )  ;   }   }  public static String get (  )   {  return MDC .get  ( TRACE_ID_KEY )  ;   }  public static void put ( String traceId )   {  MDC .put  ( TRACE_ID_KEY ,  traceId )  ;   }   } 

Filter方式和Interceptor二选其一既可,其基本思想是一样的。

Filter方式

@Component
public class LogFilter implements Filter  {  @Override
    public void doFilter ( ServletRequest servletRequest ,  ServletResponse servletResponse ,  FilterChain filterChain )  throws IOException ,  ServletException  {  TraceIdUtil .putIfAbsent  (  )  ;  // 生成trace id放入MDC中
        try  {  filterChain .doFilter  ( servletRequest ,  servletResponse )  ;   }  finally  {  TraceIdUtil .remove  (  )  ;  // 移除MDC中的trace id  }   }   } 

Interceptor

@Configuration
public class LogInterceptor implements WebMvcConfigurer  {  @Override
    public void addInterceptors ( InterceptorRegistry registry )   {  registry .addInterceptor  ( new AsyncHandlerInterceptor (  )   {  @Override
            public  boolean  preHandle ( HttpServletRequest request ,  HttpServletResponse response ,  Object handler )  throws Exception  {  TraceIdUtil .putIfAbsent  (  )  ;  // 生成trace id放入MDC中
                return AsyncHandlerInterceptor .super  .preHandle  ( request ,  response ,  handler )  ;   }  @Override
            public void afterCompletion ( HttpServletRequest request ,  HttpServletResponse response ,  Object handler ,  Exception ex )  throws Exception  {  TraceIdUtil .remove  (  )  ;  // 移除MDC中的trace id
                AsyncHandlerInterceptor .super  .afterCompletion  ( request ,  response ,  handler ,  ex )  ;   }   }  )  ;  WebMvcConfigurer .super  .addInterceptors  ( registry )  ;   }   } 

2.1.2 业务中使用

正常使用logger,无需关心trace id。例如:

@RestController
@RequestMapping (  "/api/user"  )  @Slf4j
public class UserController  {  @Autowired
    private UserService userService ;  @GetMapping (  "/{userId}"  )  public UserDto queryUser ( @PathVariable  Long  userId )   {  log .info  (  "query user by id:{}"  ,  userId )  ;  UserDto user  =  userService .query  ( userId )  ;  log .info  (  "query user result:{}"  ,  user )  ;  return user ;   }   } 

请求该接口将输出如下的日志样式:

 2022  -  04  -  05   09  :  40  :  17.638   [ http - nio -  8080  - exec -  1  ]   [ a02b13d81c224e49956afd4efbb85ca8 ]  INFO  com .nk  .webapp  .controller  .UserController   -  ready to query user  by  id :  1   2022  -  04  -  05   09  :  40  :  17.670   [ http - nio -  8080  - exec -  1  ]   [ a02b13d81c224e49956afd4efbb85ca8 ]  INFO  com .nk  .webapp  .controller  .UserController   -  query result : UserDto ( userId =  1  ,  username = zhang3 ,  age =  23  ,  email = abc@example 测试数据  ) 

四、总结

日志链路的跟踪核心是使用MDC作为trace id载体,在业务开始阶段一般通过拦截器就生成trace id并放入到MDC中,并根据MDC的相关特性将trace id投射到日志文本中,从而实现在同一个业务调用链路中的日志具有唯一标识。

原文地址:https://mp.weixin.qq测试数据/s/eEIQ6w_fYgMJ7gzkmuh3Xw

查看更多关于Java Web中日志跟踪的简单实现的详细内容...

  阅读:24次