日志链路ID配置,traceId多线程不打印什么鬼?
logback.xml 关键配置 [traceId:%X{traceId}]
<!-- 彩色日志格式模板 --><property name="log.pattern.color"value="%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level){FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=cyan, TRACE=blue} [traceId:%X{traceId}] %cyan(%logger{36}) - %msg%n"/><!-- 普通日志格式模板(非彩色) --><property name="log.pattern.plain" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [traceId:%X{traceId}] %logger{36} - %msg%n"/>
过滤器配置
@Bean@Order(Ordered.HIGHEST_PRECEDENCE + 100)@ConditionalOnMissingBean(value = TraceFilter.class)public FilterRegistrationBean <TraceFilter> traceFilterBean(){FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new TraceFilter());filterRegistrationBean.addUrlPatterns("/*");return filterRegistrationBean;}
package com.ldj.mybatisflex.filter;import com.ldj.mybatisflex.util.TraceIdUtil;
import lombok.extern.slf4j.Slf4j;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;/*** User: ldj* Date: 2025/5/13* Time: 18:39* Description: 链路过滤器*/
@Slf4j
public class TraceFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info(" Init trace filter ...");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {HttpServletRequest httpServletRequest = (HttpServletRequest) request;// 获取从网关传过来的traceIdString gatewayTraceId = httpServletRequest.getHeader(TraceIdUtil.TRACE_ID);TraceIdUtil.checkAndSaveTraceId(gatewayTraceId);chain.doFilter(request, response);} catch (Exception e) {e.printStackTrace();}finally {TraceIdUtil.removeTraceId();}}@Overridepublic void destroy() {log.info(" Destroy trace filter ...");}
}
id工具类
package com.ldj.mybatisflex.util;import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;import java.util.UUID;/*** User: ldj* Date: 2025/5/13* Time: 14:37* Description: 链路id工具类*/
public class TraceIdUtil {public static final String TRACE_ID = "traceId";/*** 自动生成链路id*/public static String buildTraceId() {return UUID.randomUUID().toString().replaceAll("-", "");}/*** 移除链路id*/public static void removeTraceId() {MDC.remove(TRACE_ID);}/*** 检验生成链路id*/public static void checkAndSaveTraceId(String traceId) {MDC.put(TRACE_ID, StringUtils.isBlank(traceId) ? buildTraceId() : traceId);}/*** 获取链路id*/public static String getTraceId() {return MDC.get(TRACE_ID);}
}
统一响应类
package com.ldj.mybatisflex.common;import com.ldj.mybatisflex.util.TraceIdUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.context.i18n.LocaleContextHolder;import java.util.ResourceBundle;/*** User: ldj* Date: 2025/1/12* Time: 18:08* Description: 统一响应类*/
@Getter
@Setter
public class ApiResponse<T> {private static final String basePath = "i18n/message";private static final Integer successCode = 200;private static final String success = "成功!";private static final Integer failCode = 500;private static final String fail = "失败!";private Integer code;private String message;// 过滤器会往MDC存入traceIdprivate String traceId;private T data;public static <T> ApiResponse<T> success(T data) {ApiResponse<T> apiResponse = new ApiResponse<>();apiResponse.setCode(200);apiResponse.setMessage(success);apiResponse.setTraceId(TraceIdUtil.getTraceId());apiResponse.setData(data);return apiResponse;}public static <T> ApiResponse<T> fail() {return fail(failCode, fail);}//关键代码是读取国际化的配置文件,作为错误提示消息public static <T> ApiResponse<T> fail(ExceptionEnum exceptionEnum, T date) {ApiResponse<T> apiResponse = new ApiResponse<>();apiResponse.setCode(exceptionEnum.getCode());ResourceBundle bundle = ResourceBundle.getBundle(basePath, LocaleContextHolder.getLocale());String message = bundle.getString(exceptionEnum.getCode().toString());apiResponse.setMessage(message);apiResponse.setTraceId(TraceIdUtil.getTraceId());apiResponse.setData(date);return apiResponse;}public static <T> ApiResponse<T> fail(Integer code, String message) {ApiResponse<T> apiResponse = new ApiResponse<>();apiResponse.setCode(code);apiResponse.setMessage(message);apiResponse.setTraceId(TraceIdUtil.getTraceId());apiResponse.setData(null);return apiResponse;}
}
测试效果:
后续补充:
1. 对于多线程程序,如何解决链路id不能在子线程里传递?
解决方法:
package com.ldj.mybatisflex.config;import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;/*** User: ldj* Date: 2025/5/13* Time: 20:35* Description: 解决@Async 链路id不能在子线程传递的问题*/
@Configuration
public class AsyncConfig extends AsyncConfigurerSupport {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(8);taskExecutor.setMaxPoolSize(50);taskExecutor.setQueueCapacity(100);taskExecutor.setKeepAliveSeconds(5);taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());taskExecutor.setThreadNamePrefix("async-pool-");// 自定义处理增强方法taskExecutor.setTaskDecorator(task -> {// 获取主线程的 MDC 上下文(map 形式)Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();return () -> {try {// 如果存在上下文,则将主线程的 traceId 等信息传递给线程池的子线程if (!copyOfContextMap.isEmpty()) {MDC.setContextMap(copyOfContextMap);}task.run();} finally {MDC.clear();}};});taskExecutor.initialize();return taskExecutor;}}
效果:
2. 对于消息队列,如何解决生产者端的链路id不能在消费者端传递?
解决思路: 消息发送前做增强,消息被消费时做增强,具体是有增强扩张点在扩展点做增强,或者期切mq相关注解拿到消息如Message。
我这里演示RocketMq,如果RabbatMq 发送消息时有前置函数,消费时可切@RabbitListener注解
拿到Message 从而拿到traceId,然后放入MDC。
发送端改造.
首先,我们需要创建一个 SendMessageHook
来在发送消息之前添加 traceId
到消息属性中。
public class TraceIdSendMessageHook implements SendMessageHook {@Overridepublic String hookName() {return "TraceIdSendMessageHook";}@Overridepublic void sendMessageBefore(SendMessageContext context) {// 从MDC获取traceIdString traceId = MDC.get("traceId");if (traceId == null || traceId.isEmpty()) {traceId = UUID.randomUUID().toString(); // 如果没有traceId,则生成一个新的MDC.put("traceId", traceId);}// 将traceId放入消息属性context.getMessage().putUserProperty("TRACE_ID", traceId);}@Overridepublic void sendMessageAfter(SendMessageContext context) {// 可以在此处进行发送后的处理,如日志记录等}
}
然后,在 Broker 的配置文件 broker.conf
中注册这个 Hook
消费端改造
接下来,我们需要在消费端从消息中提取 traceId
并设置到 MDC 中。为此,可以使用 ConsumeMessageHook
。
public class TraceIdConsumeMessageHook implements ConsumeMessageHook {@Overridepublic String hookName() {return "TraceIdConsumeMessageHook";}@Overridepublic void consumeMessageBefore(ConsumeMessageContext context) {Message message = context.getMessage();String traceId = message.getUserProperty("TRACE_ID");if (traceId != null) {MDC.put("traceId", traceId); // 设置到MDC}}@Overridepublic void consumeMessageAfter(ConsumeMessageContext context) {// 清理MDCMDC.clear();}
}
在 Broker 的配置文件 broker.conf 中注册2个Hook:
sendMessageHook=TraceIdSendMessageHook
consumeMessageHook=TraceIdConsumeMessageHook