通过logback日志简单实现链路追踪

流程解析:让所有经过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-8
      enabled: true
      force: 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 {
    @Override
    public 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";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果有上层调用就用上层的ID
        String traceId = request.getHeader(TRACE_ID);
        if (traceId == null) {
            traceId = IdUtil.objectId();
        }
        MDC.put(TRACE_ID, traceId);
        response.addHeader(TRACE_ID, traceId);
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        MDC.remove(TRACE_ID);
    }

}
相关推荐
考虑考虑3 分钟前
Sentinel安装
java·后端·微服务
IT_陈寒6 分钟前
SpringBoot自动配置失灵?你可能忘了这个关键注解
前端·人工智能·后端
逝水无殇23 分钟前
C# 文件的输入与输出详解
开发语言·数据库·后端·c#
YIAN1 小时前
从手写 Agent 工具到 MCP 协议:一文搞懂大模型跨进程跨语言工具调用
后端·agent·mcp
小兔崽子去哪了1 小时前
Docker 删除镜像后磁盘空间没有释放?
后端·docker·容器
凤凰院凶涛QAQ1 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
高明珠2 小时前
麒麟 V10 SP1 排查实录:ttyS5 每 10 秒莫名收到一批报文,元凶是 eGTouchD
后端
卷无止境2 小时前
Python FFI 技术深度解析:ctypes、cffi 与 pybind11 的性能差异与实践挑战
后端·python
ServBay2 小时前
AI Gateway 是什么?为什么每个平台都在做
后端·aigc·ai编程
SamDeepThinking2 小时前
Vibe Coding最重要的是Spec
后端·程序员·ai编程