# 任务:TraceId 链路追踪与上下文传播能力 你现在需要为我的 Java 单体项目实现一套“统一 TraceId 链路追踪基础设施”。 ## 一、项目背景 当前项目是 Java 单体应用,技术栈: - JDK 8 - Spring Boot 2.5.x - Spring MVC - Logback / SLF4J - MySQL - Redis - Quartz - MQ - Netty - MQTT - 后续会接入 Python AI 服务、LLM、微服务、OpenTelemetry 项目未来会逐步发展为: ```text 单体应用 ↓ IoT + SaaS + AI ↓ 微服务 ↓ OpenTelemetry / 分布式链路追踪 ``` 因此这次实现不能只是简单地增加一个 MDC 字段,而要设计成可长期演进的基础框架。 --- # 二、核心目标 实现统一的: ```text TraceContext ``` 核心字段至少包括: ```text traceId tenantId userId ``` 其中当前阶段最重要的是: ```text traceId ``` 核心原则: > 一次完整业务执行链使用同一个 traceId。 例如: ```text 前端请求 ↓ Controller ↓ Service ↓ 线程池 ↓ MQ ↓ MQ Consumer ↓ AI 服务 ↓ 数据库 ↓ 通知 ``` 如果它们属于同一次业务执行,则全部使用: ```text traceId = A ``` --- # 三、TraceId 生命周期规则 必须严格遵循以下规则。 ## 1. 有上游 TraceId 优先继承。 例如 HTTP: ```http X-Trace-Id: 01KABC... ``` 后端收到后: ```text TraceId = 01KABC... ``` 不要重新生成。 --- ## 2. 没有上游 TraceId 自动创建新的 TraceId。 例如: ```text 浏览器 ↓ HTTP ↓ Java ``` 如果 HTTP 没有: ```http X-Trace-Id ``` 则 Java 自动生成。 --- ## 3. 定时任务没有上游 TraceId Quartz / @Scheduled 等任务启动时自动生成: ```text traceId ``` 例如: ```text traceId=T202609150001 ``` 整个任务执行过程保持相同 TraceId。 --- # 四、TraceId 生成器 设计独立: ```java TraceIdGenerator ``` 不要把 TraceId 生成逻辑写死在 Filter 中。 要求: ```text TraceContext TraceIdGenerator ``` 职责分离。 优先考虑 ULID 或项目中已有的高质量 ID 生成方案。 要求: - 唯一 - 高并发安全 - 尽量时间有序 - 适合日志检索 - 不依赖数据库 - 后续可以替换实现 --- # 五、TraceContext 设计 创建统一: ```java TraceContext ``` 例如: ```text TraceContext ├── getTraceId() ├── setTraceId() ├── getOrCreateTraceId() ├── set() ├── get() ├── clear() └── snapshot() ``` 不要让业务代码直接到处: ```java MDC.put("traceId", ...) ``` 业务代码应该依赖: ```java TraceContext ``` MDC 只是 TraceContext 的一种底层日志传播实现。 核心原则: ```text TraceContext ≠ MDC ``` 而应该是: ```text TraceContext ↓ MDC HTTP Header MQ Header ThreadPool Future ``` --- # 六、Web 请求 实现 Spring MVC Filter。 建议: ```java OncePerRequestFilter ``` 功能: 1. 从 HTTP Header 获取: ```http X-Trace-Id ``` 2. 如果存在且合法: ```text 继续使用 ``` 3. 如果不存在: ```text 生成 TraceId ``` 4. 放入: ```text TraceContext MDC ``` 5. 将 TraceId 写入 HTTP Response Header: ```http X-Trace-Id: 01KABC... ``` 6. 请求结束后必须清理: ```text MDC TraceContext ``` 防止 Tomcat 线程池复用造成 TraceId 污染。 --- # 七、日志输出 修改 Logback 日志格式,使所有日志自动包含: ```text traceId ``` 例如: ```text 2026-09-15 11:20:31.123 INFO [http-nio-8080-exec-5] traceId=01KABC123 DeviceService - 查询设备成功 deviceId=DEV001 ``` 要求: - 控制台日志包含 traceId - 普通文件日志包含 traceId - error 日志包含 traceId - 不要求业务代码修改每一条 log.info() - 使用 MDC 自动完成 同时考虑 JSON 日志扩展能力。 --- # 八、线程池上下文传播 这是本任务的重点。 普通: ```java ThreadPoolExecutor ``` 或者: ```java CompletableFuture ``` 不能保证 MDC 自动传播。 必须实现统一的上下文传播机制。 例如: ```text TraceContext ↓ snapshot ↓ Runnable / Callable ↓ restore ↓ 执行 ↓ clear ``` 要求实现类似: ```text TraceContextRunnable TraceContextCallable TraceableExecutor ``` 或者使用 Spring 提供的: ```text TaskDecorator ``` 如果项目使用 Spring ThreadPoolTaskExecutor,优先通过: ```text TaskDecorator ``` 实现。 目标: ```text Web线程 traceId=A ↓ 线程池 traceId=A ``` 例如: ```java executor.execute(() -> { log.info("异步处理"); }); ``` 日志必须仍然是: ```text traceId=A ``` --- # 九、CompletableFuture 重点处理: ```java CompletableFuture.runAsync(...) CompletableFuture.supplyAsync(...) thenApply(...) thenAccept(...) thenRun(...) ``` 分析项目中现有异步代码。 不要简单粗暴地修改所有业务代码。 优先通过统一线程池: ```text TraceableExecutor ``` 解决。 如果无法完全覆盖 CompletableFuture 的默认线程池,需要明确指出风险,并提供统一解决方案。 禁止让业务代码到处手动: ```java MDC.put(...) MDC.clear(...) ``` --- # 十、MQ TraceId 传播 这是第二个重点。 当 Java 发送 MQ 消息时: 必须把当前: ```text traceId ``` 写入 MQ Message Header / Properties。 例如: ```text traceId=A ``` 发送: ```text MQ Message ├── Header │ └── traceId=A └── Body ``` Consumer 收到消息: ```text MQ Header ↓ 读取 traceId ↓ TraceContext.setTraceId() ↓ MDC ↓ 业务处理 ``` 这样: ```text Web traceId=A ↓ MQ Producer traceId=A ↓ MQ traceId=A ↓ Consumer traceId=A ``` 如果 MQ 消息没有 TraceId: ```text Consumer ↓ 自动生成新的 TraceId ``` 但正常情况下必须继承 Producer 的 TraceId。 --- # 十一、MQ 不同消息中间件 先检查当前项目实际使用的 MQ。 不要凭空假设。 如果项目目前只有一种 MQ: 优先实现当前实际使用的 MQ。 同时将设计抽象成: ```text TraceMessageInterceptor ``` 未来可以支持: ```text RabbitMQ Kafka RocketMQ Redis Stream 其他 MQ ``` 不要为了扩展性一次性实现大量没有使用的 MQ。 --- # 十二、HTTP 调用链 如果 Java 单体项目会调用其他服务: 例如: ```text Java ↓ HTTP Python AI ``` 必须自动传递: ```http X-Trace-Id: A ``` 如果项目使用: ```text RestTemplate WebClient OpenFeign HttpClient OkHttp ``` 检查实际使用情况。 优先通过统一 Interceptor 实现。 例如: ```text Java traceId=A ↓ HTTP Header X-Trace-Id=A ↓ Python AI traceId=A ``` 这样 Java 和 Python 日志可以使用同一个 TraceId 检索。 --- # 十三、Netty / MQTT / TCP / UDP 这是 IoT 项目的重要场景。 检查项目现有: ```text Netty MQTT TCP UDP CoAP ``` 入口。 对于每一个独立的消息 / 请求: 如果存在上游 TraceId: ```text 继承 ``` 否则: ```text 生成 ``` 然后进入: ```text TraceContext ``` 例如: ```text 设备 DEV001 ↓ MQTT ↓ traceId=M001 ↓ 数据解析 ↓ 物模型 ↓ 数据库 ↓ 告警 ``` 整条处理链使用: ```text traceId=M001 ``` 注意: > 设备每次独立上报消息属于独立业务事件,因此通常应该生成新的 TraceId,而不是让一个设备永久使用同一个 TraceId。 --- # 十五、SaaS 上下文 项目未来是 SaaS,因此 TraceContext 预留: ```text traceId tenantId userId ``` 如果当前项目已经有: ```text tenantId userId ``` 则在请求认证成功后自动放入上下文。 例如: ```text traceId=A tenantId=10001 userId=9527 ``` 但不要因为本次任务而大范围修改现有权限系统。 只需要提供统一上下文承载能力。 --- # 十六、线程安全 这是重点。 必须考虑: ```text Tomcat线程池复用 Quartz线程池 业务线程池 CompletableFuture MQ Consumer线程 Netty EventLoop ``` 任何任务结束后都必须: ```text clear ``` 禁止出现: ```text 请求A traceId=A 请求结束 请求B traceId=A ``` 这种严重的数据串链问题。 --- # 十七、异常处理 异常情况下 TraceId 也必须保留。 例如: ```text traceId=A ERROR 设备数据处理失败 ``` 完整异常堆栈必须能够通过: ```text traceId=A ``` 检索出来。 Web 异常响应最好也返回: ```http X-Trace-Id: A ``` 这样前端报错: ```text TraceId: A ``` 我就可以直接去 Kibana / 日志文件搜索: ```text traceId=A ``` 定位问题。 --- # 十八、日志检索目标 最终必须支持: ```bash grep "traceId=01KABC" app.log ``` 或者 Kibana: ```text traceId:"01KABC" ``` 一次找到: ```text HTTP请求 Service 数据库 线程池 MQ Producer MQ Consumer AI 异常 ``` --- # 十九、代码结构要求 根据当前项目实际结构,合理放置。 如果存在类似: ```text common framework core starter ``` 优先放到公共基础模块。 推荐结构: ```text xxx-core └── trace ├── TraceContext ├── TraceIdGenerator ├── TraceContextSnapshot ├── TraceContextRunnable └── TraceContextCallable xxx-web-starter └── trace └── TraceIdFilter xxx-log-starter └── trace └── MDC / Logback xxx-schedule-starter └── trace └── Quartz Trace xxx-mq-starter └── trace ├── Producer Trace └── Consumer Trace ``` 具体目录必须根据当前项目实际结构调整,不要机械套用。 --- # 二十、不要过度设计 当前项目是单体项目。 第一阶段: 必须实现: ```text TraceId MDC Web ThreadPool Quartz MQ HTTP ``` 暂时不要自行实现完整: ```text Span Trace Graph Jaeger Zipkin SkyWalking OpenTelemetry Collector ``` 但是代码设计要为未来 OpenTelemetry 留出扩展点。 也就是说: ```text 现在: TraceId + MDC 未来: TraceId + SpanId + ParentSpanId + OpenTelemetry ``` 不要把当前 TraceContext 设计成未来无法迁移的死结构。 --- # 二十一、需要先做代码分析 在开始修改代码之前: 1. 扫描整个项目结构。 2. 找到 Spring Boot 启动模块。 3. 找到 Web 请求入口。 4. 找到现有 Filter / Interceptor。 5. 找到现有线程池。 6. 找到 CompletableFuture。 7. 找到 Quartz / @Scheduled。 8. 找到 MQ Producer。 9. 找到 MQ Consumer。 10. 找到 RestTemplate / Feign / HTTP Client。 11. 找到 Netty / MQTT / TCP / UDP 入口。 12. 找到 Logback 配置。 13. 找到现有 MDC / ThreadLocal / RequestContext 实现。 14. 检查项目是否已经存在类似 TraceId / RequestId 机制。 如果已经存在类似能力: > 优先重构和统一,不要重复造轮子。 --- # 二十二、实现原则 遵守: ```text 低侵入 高复用 自动传播 自动清理 统一入口 统一出口 ``` 业务代码尽可能不需要修改。 理想效果: 原来的代码: ```java log.info("开始处理设备"); ``` 完全不用修改。 自动变成: ```text traceId=A 开始处理设备 ``` --- # 二十三、测试要求 实现完成后必须增加测试。 至少测试: ### Test 1:HTTP ```text 请求进入 ↓ 生成 TraceId ↓ Controller ↓ Service ↓ 日志 ↓ Response Header ``` 确认 TraceId 一致。 ### Test 2:HTTP携带 TraceId ```text X-Trace-Id=A ``` 确认后端仍然使用: ```text A ``` ### Test 3:线程池 ```text Web traceId=A ↓ ThreadPool ``` 确认: ```text traceId=A ``` ### Test 4:MQ ```text Producer traceId=A ↓ MQ Header ↓ Consumer ``` 确认 Consumer: ```text traceId=A ``` ### Test 5:MQ无 TraceId 确认 Consumer 自动生成。 ### Test 6:定时任务 确认: ```text TASK traceId=A ``` 整个任务一致。 ### Test 7:线程池污染 执行: ```text 请求A 请求B 请求C ``` 确认: ```text A ≠ B ≠ C ``` 并且不会出现线程复用导致 TraceId 串线。 ### Test 8:异常 异常发生后: ```text traceId ``` 仍然存在于 ERROR 日志。 ### Test 9:并发 同时执行大量请求: ```text 100+ 1000+ ``` 确认 TraceId 不串线。 --- # 二十四、最终输出要求 完成后请给我: ## 1. 修改文件列表 例如: ```text 新增: xxx/TraceContext.java 修改: xxx/Logback.xml xxx/xxxFilter.java ``` ## 2. 架构说明 说明: ```text Web ↓ TraceContext ↓ MDC ThreadPool ↓ Context Propagation MQ ↓ Message Header HTTP ↓ X-Trace-Id ``` ## 3. 使用示例 告诉我业务代码以后应该怎么写。 目标是: ```java log.info("开始处理"); ``` 而不是让业务代码手动管理 TraceId。 ## 4. 测试结果 说明所有测试是否通过。 ## 5. 风险 明确指出: - 哪些场景已经支持 - 哪些场景暂未支持 - 是否存在 MDC 丢失风险 - 是否存在线程池上下文污染风险 - 是否存在 MQ TraceId 丢失风险 ## 6. 后续演进建议 最后说明未来如何从: ```text TraceId + MDC ``` 演进到: ```text TraceId + SpanId + ParentSpanId + OpenTelemetry ``` 但本次不要提前实现完整分布式链路追踪系统。 --- # 最终验收标准 只有同时满足以下条件才算完成: ```text traceId=A │ ┌───────┴───────┐ │ │ Web Task │ ▼ Service │ ┌───────┴───────┐ ▼ ▼ ThreadPool MQ │ │ │ ▼ │ Consumer │ │ └───────┬───────┘ ▼ AI/HTTP │ ▼ DB ``` 同一业务链可以通过: ```text traceId=A ``` 在日志文件、IDEA Console、Kibana 中完整检索。 尤其必须保证: > **跨线程、跨线程池、跨 MQ、跨 HTTP 的 TraceId 不丢失;任务执行完成后上下文必须清理,不允许线程复用造成 TraceId 污染。** 请先分析当前代码,再实施修改。 不要在没有检查项目现有实现的情况下直接创建重复的 TraceId / MDC / ThreadLocal 体系。