Springboot 集成 TraceID

要在 Spring Boot 2.5.15 中实现日志打印包含 traceID 的功能,最简便的方式是使用 Spring Cloud Sleuth。它能自动生成和传播 traceID,并将其嵌入到日志中,无需手动处理。以下是具体实现步骤:

一、核心原理

Spring Cloud Sleuth 会通过 MDC(Mapped Diagnostic Context)机制,将生成的 traceID 和 spanID 存储到线程上下文中。我们只需在日志格式中配置从 MDC 中获取这些信息,即可让每条日志都自动带上 traceID。

二、具体实现步骤

1. 添加依赖(pom.xml)

pom.xml 中引入 Sleuth 依赖(无需 Zipkin,仅需日志中的 traceID 时):

xml

复制代码
<!-- 引入 Spring Cloud BOM 管理版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2020.0.6</version> <!-- 与 Spring Boot 2.5.15 匹配 -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- Spring Cloud Sleuth:生成并传递 traceID -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<!-- 确保有日志依赖(Spring Boot 默认已包含) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

2. 配置日志格式(application.yml)

在配置文件中修改日志格式,添加从 MDC 中获取 traceIdspanId 的占位符:

yaml

复制代码
spring:
  application:
    name: your-service-name # 服务名称(用于链路标识)
  sleuth:
    sampler:
      probability: 1.0 # 采样率 100%(开发环境建议,生产可按需调整)
    baggage:
      correlation-fields: traceId,spanId # 关联日志字段

# 日志配置:关键是添加 [%X{traceId:-},%X{spanId:-}] 占位符
logging:
  pattern:
    # 控制台日志格式
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{50} - %msg%n"
    # 文件日志格式(如果需要输出到文件)
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{50} - %msg%n"
  # 日志文件路径(可选)
  file:
    name: logs/app.log
配置说明:
  • [%X{traceId:-},%X{spanId:-}]%X{key} 是从 MDC 中获取值的日志占位符,:- 表示如果获取不到则显示空(避免报错)。
  • spring.sleuth.sampler.probability:采样率设为 1.0 表示所有请求都生成 traceID,适合开发和测试环境。

3. 验证代码(无需额外编码)

Sleuth 会自动拦截所有 HTTP 请求、异步任务等,并注入 traceID。只需正常编写业务代码即可:

java

运行

复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
    private static final Logger log = LoggerFactory.getLogger(DemoController.class);

    @GetMapping("/test")
    public String testTraceId() {
        log.info("这是一条包含 traceID 的日志");
        log.warn("这是一条警告日志,同样包含 traceID");
        return "查看控制台日志,检查是否包含 traceID";
    }
}

三、验证效果

  1. 启动 Spring Boot 应用;
  2. 访问接口 http://localhost:8080/test
  3. 查看控制台日志,会看到类似如下输出:

plaintext

复制代码
2024-08-25 10:15:30.123 [http-nio-8080-exec-1] INFO  [abc1234567890,def7890123456] com.example.DemoController - 这是一条包含 traceID 的日志
2024-08-25 10:15:30.125 [http-nio-8080-exec-1] WARN  [abc1234567890,def7890123456] com.example.DemoController - 这是一条警告日志,同样包含 traceID

其中 abc1234567890 就是本次请求的全局唯一 traceID,所有相关日志都会携带相同的 traceID。

四、特殊场景处理

1. 异步方法日志包含 traceID

默认情况下,Sleuth 会处理 @Async 注解的异步方法,但需确保异步配置正确:

java

运行

复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.initialize();
        return executor;
    }
}

使用异步方法时,日志会自动携带 traceID:

java

运行

复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class DemoService {
    private static final Logger log = LoggerFactory.getLogger(DemoService.class);

    @Async
    public void asyncMethod() {
        log.info("异步方法中的日志,应包含 traceID");
    }
}

2. 手动获取 traceID

如果需要在代码中手动获取 traceID(如返回给前端),可注入 Tracer 工具类:

java

运行

复制代码
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TraceIdController {
    private final Tracer tracer;

    // 构造注入 Tracer
    public TraceIdController(Tracer tracer) {
        this.tracer = tracer;
    }

    @GetMapping("/get-trace-id")
    public String getTraceId() {
        // 获取当前 traceID
        String traceId = tracer.currentSpan().context().traceIdString();
        return "当前请求的 traceID 是:" + traceId;
    }
}

场景二:

原始项目有logback配置日志样式,当前logging 配置发现没有生效,没有生成 logs/app.log 并且 新打印日志并没有注入traceID和spenID 问题解决如下:

当项目中存在 logback.xml 配置时,Spring Boot 的 application.ymllogging 相关配置可能会被覆盖,导致 TraceID 无法注入日志。这是因为 Logback 配置文件的优先级高于 Spring Boot 全局配置。以下是具体解决方案:

一、核心原因分析

  1. 配置优先级冲突logback.xml 是 Logback 的原生配置文件,优先级高于 Spring Boot 的 application.yml 中的 logging 配置,导致 application.yml 中定义的日志格式(含 TraceID)和文件路径不生效。
  2. TraceID 注入依赖 Logback 配置 :Sleuth 通过 MDC 注入的 traceIdspanId,需要在 Logback 的日志格式中显式配置才能输出。

二、解决方案:修改 logback.xml 配置

需直接在 logback.xml 中添加 TraceID/SpanID 占位符,并配置日志文件输出路径,确保与 Sleuth 集成。

1. 找到项目中的 logback.xml

通常位于 src/main/resources/logback.xml,日志中提示存在多个 logback.xml(如 newauditor-adminnewauditor-drools 模块),需确保修改的是当前启动模块使用的 logback.xml

2. 修改 logback.xml 配置

在日志格式中添加 [%X{traceId:-},%X{spanId:-}] 占位符,并配置文件输出路径。示例完整配置:

xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!-- 定义日志输出格式(核心:添加 traceId 和 spanId) -->
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{traceId:-},%X{spanId:-}] %logger{50} - %msg%n" />
    
    <!-- 定义日志文件存储路径 -->
    <property name="LOG_PATH" value="logs" />
    <property name="LOG_FILE_NAME" value="app" />

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 使用含 TraceID 的格式 -->
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!--  info 级别日志文件输出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件路径:logs/app.log -->
            <fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志保留天数 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- warn 级别日志文件输出 -->
    <appender name="file_warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-warn.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- error 级别日志文件输出 -->
    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}-error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="file_info" />
        <appender-ref ref="file_warn" />
        <appender-ref ref="file_error" />
    </root>

    <!-- 自定义包日志级别(可选) -->
    <logger name="com.newtouch.ai.newauditor" level="INFO" additivity="false">
        <appender-ref ref="console" />
        <appender-ref ref="file_info" />
    </logger>
</configuration>

3. 关键配置说明

  • 日志格式[%X{traceId:-},%X{spanId:-}] 是从 MDC 中获取 Sleuth 注入的 TraceID 和 SpanID,:- 表示如果获取不到则显示空(避免日志报错)。
  • 日志路径 :通过 LOG_PATHLOG_FILE_NAME 定义日志文件路径为 logs/app.log,与 application.yml 中期望的路径一致。
  • 输出级别 :确保 root 或自定义包的日志级别至少为 INFO(避免日志被过滤)。

三、验证配置是否生效

  1. 删除冲突配置 :若 application.yml 中仍有 logging 配置,建议注释或删除(避免与 logback.xml 冲突)。
  2. 重启项目 :启动后观察:
    • 控制台日志是否包含 [traceId,spanId] 格式(如 [a1b2c3d4e5f67890,a1b2c3d4e5f67890]);
    • 项目根目录下是否生成 logs 文件夹及 app.log 文件;
    • 访问接口后,检查日志文件中是否有带 TraceID 的日志。

四、特殊场景处理

1. 多模块项目 logback.xml 冲突

日志中提示 logback.xml 存在多个(如 newauditor-adminnewauditor-drools):

  • 确保启动模块(如 newauditor-admin)的 logback.xml 是最终生效的配置;
  • 其他模块的 logback.xml 可删除或统一继承主模块配置(通过 <include> 标签引入)。

2. 异步线程日志不显示 TraceID

若项目中使用异步线程(如 @Async),需配置 Sleuth 对异步线程的支持:

java

运行

复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        // 关键:设置线程名称前缀,便于日志追踪
        executor.setThreadNamePrefix("Async-");
        // 初始化线程池
        executor.initialize();
        return executor;
    }
}

场景三:显著追加traceID,和 spanID字段

复制代码
<property name="log.pattern" value="%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [traceID:%X{traceId:-},spanID:%X{spanId:-}] %logger{20} - [%method,%line] - %msg%n" />

总结

核心是通过修改 logback.xml 配置,在日志格式中添加 TraceID/SpanID 占位符,并正确配置日志文件路径。由于 logback.xml 优先级高于 application.yml,必须在此文件中完成所有日志相关配置,才能确保 Sleuth 生成的 TraceID 正常输出到日志中。

五、总结

通过以上配置,Spring Boot 应用的所有日志(包括控制器、服务、工具类等)都会自动包含 traceID,实现了:

  1. 单条请求的所有日志通过同一个 traceID 关联;
  2. 跨服务调用时 traceID 自动传递(如果有多个服务);
  3. 无需手动埋点,Sleuth 自动处理。

这种方式既能满足日志查询和问题定位的需求,又能最小化代码侵入性。bo t

相关推荐
CHENFU_JAVA几秒前
使用EasyExcel实现Excel单元格保护:自由锁定表头和数据行
java·excel
奔跑吧邓邓子12 分钟前
Spring Boot实战:打造高效Web应用,从入门到精通
spring boot·实战·入门到精通
青云交1 小时前
Java 大视界 -- 基于 Java 的大数据实时流处理在智能电网分布式电源接入与电力系统稳定性维护中的应用(404)
java·大数据·分布式·智能电网·flink 实时流处理·kafka 数据采集·iec 61850 协议
仰望星空@脚踏实地2 小时前
maven scope 详解
java·maven·scope
M_Reus_112 小时前
Groovy集合常用简洁语法
java·开发语言·windows
带刺的坐椅3 小时前
10分钟带你体验 Solon 的状态机
java·solon·状态机·statemachine
小鹅叻3 小时前
MyBatis题
java·tomcat·mybatis
RainbowSea3 小时前
4. LangChain4j 模型参数配置超详细说明
java·langchain·ai编程
RainbowSea3 小时前
3. LangChain4j + 低阶 和 高阶 API的详细说明
java·llm·ai编程
叫我阿柒啊3 小时前
Java全栈开发面试实战:从基础到微服务的深度探索
java·spring boot·redis·微服务·vue3·全栈开发·面试技巧