流程解析:让所有经过Controller的方法都有一个唯一ID,我们往logback日志的MDC里面加我们的唯一ID,然后在配置文件里面指定我们的logback日志输出的格式,这样子我们输出的日志里面就有ID了,我们可以根据这个ID定位我们的想找到的日志方便我们debug
需要用到的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency><!-- Hutool工具包 -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency><!-- SLF4J日志 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId>
</dependency><!-- Lombok 依赖 -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope>
</dependency><!-- SLF4J API 依赖 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.9</version>
</dependency><!-- SLF4J 实现(例如 Logback)依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.4.11</version>
</dependency>
Spring配置类配置日志输出格式
配置输出格式,让我们自己加的TraceId可以成功输出
logging:pattern:console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %X{traceId} %logger{36} - %msg%n"server:servlet:encoding:charset: UTF-8enabled: trueforce: true
AOP配置类
生成我们的TraceId放到我们的slf4j的MDC里面
注意为什么我们的只在Controller下往我们的MDC里面加东西呢
因为我们的MDC是共享上下文的,它会往下传递,我们不需要再匹配Service方法再生成一个ID
package com.example.threadpool.Config;import cn.hutool.core.util.IdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ControllerLogAspect {public static final String TRACE_ID = "traceId";private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);// 定义切入点,匹配带有 @Controller、@RestController 或 @Service 注解的类中的所有方法@Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController) || @within(org.springframework.stereotype.Service)")public void controllerAndServiceMethods() {}@Around("controllerAndServiceMethods()")public Object around(ProceedingJoinPoint point) throws Throwable {try {String traceId = IdUtil.objectId();String fullTraceId = "追踪ID:" + traceId;MDC.put(TRACE_ID, fullTraceId);logger.info("Generated traceId: {}", fullTraceId); // 调试日志return point.proceed();} finally {MDC.remove(TRACE_ID);}}
}
Controller测试类
package com.example.threadpool.Config;import cn.hutool.core.util.IdUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ControllerLogAspect {public static final String TRACE_ID = "traceId";private static final Logger logger = LoggerFactory.getLogger(ControllerLogAspect.class);// 定义切入点,只匹配带有 @RestController 注解的类中的所有方法@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")public void controllerMethods() {}@Around("controllerMethods()")public Object aroundController(ProceedingJoinPoint point) throws Throwable {try {String traceId = IdUtil.objectId();String fullTraceId = "追踪ID:" + traceId;MDC.put(TRACE_ID, fullTraceId);logger.info("Generated traceId: {}", fullTraceId); // 调试日志return point.proceed();} finally {MDC.remove(TRACE_ID);}}
}
Service测试类
package com.example.threadpool.Config;public interface TestService {public String test();
}
package com.example.threadpool.Config;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;@Controller
@Slf4j
public class TestServiceImpl implements TestService {@Overridepublic String test() {log.info("这是Service的日志");return null;}
}
输出结果
通过拦截器和请求头实现不同服务之间TraceID的传递
package com.achobeta.intercepter;import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class LogInterceptor implements HandlerInterceptor {public static final String TRACE_ID = "traceId";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上层调用就用上层的IDString traceId = request.getHeader(TRACE_ID);if (traceId == null) {traceId = IdUtil.objectId();}MDC.put(TRACE_ID, traceId);response.addHeader(TRACE_ID, traceId);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {MDC.remove(TRACE_ID);}}