您现在的位置是:亿华云 > 域名

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

亿华云2025-10-02 18:42:00【域名】3人已围观

简介一、前言在编码过程中,常常需要写打印日志语句,我们期望的是同一个业务的日志都在一块,在出问题的时候好根据日志来排查问题。而现实是在应用运行中,日志的输出常常来自不同线程,甚至是在不同微服务中,各种日志

一、日志前言

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

二、MDC介绍

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

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

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

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

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

代码如下:

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

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}] %-5level %logger{ 50} - %msg%n

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

2.1.2 业务开始

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.com)

四、总结

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

很赞哦!(2888)