1.traceId用途
主要用于项目dubbo接口调用链日志追踪使用,可以获取完整的链路日志,协助排查问题。
2.traceId传递及代码实现
本方案是基于 org.slf4j.MDC 进行实现,会出现线程池中线程复用导致traceId重复问题,后面会说解决方案。
-
web项目(CONSUMER)
- com.alibaba.dubbo.rpc.Filter 文件,路径在src\main\resources\META-INF\dubbo目录下面,文件内容就是对应项目中指定的过滤器类路径
-
globalTraceFilter=com.xxx.filter.GlobalTraceFilter
-
- com.alibaba.dubbo.rpc.Filter 文件,路径在src\main\resources\META-INF\dubbo目录下面,文件内容就是对应项目中指定的过滤器类路径
-
GlobalTraceFilter类里面代码如下,实现dubbo的Filter接口:
package com.xxx.filter; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.*; import org.slf4j.MDC; import java.util.UUID; /** * @Description 过滤器传递tradeId(消费者) * @Version v1.0 */ @Activate(group = {CommonConstants.CONSUMER}) @Slf4j public class GlobalTraceFilter implements Filter { private static final String TRACE_ID = "TraceId"; @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { RpcContext rpcContext = RpcContext.getContext(); String traceId; if (rpcContext.isConsumerSide()) { traceId = MDC.get(TRACE_ID); if (traceId == null) { traceId = UUID.randomUUID().toString().replace("-", ""); } MDC.put(TRACE_ID, traceId); rpcContext.setAttachment(TRACE_ID, traceId); } return invoker.invoke(invocation); } }
-
logback-spring.xml配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <springProperty scope="context" name="PORT" source="server.port" default = ""/> <property name="log_dir" value="/data/logs/sso/${PORT}" /> <property name="default_log" value="${log_dir}/xxx-web"/> <property name="error_log" value="${log_dir}/xxx-web-error"/> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder charset="UTF-8"> <pattern> %d{yyyy-MM-dd HH:mm:ss.SSS},[%X{TraceId}] [%thread] %highlight(%-5level) %cyan(%logger{15}) - %highlight(%msg) %n%exception </pattern> </encoder> </appender> <!-- 默认日志 按日切分 --> <appender name="default" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${default_log}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${default_log}.%d{yyyy-MM-dd}</fileNamePattern> <maxHistory>60</maxHistory> <totalSizeCap>50GB</totalSizeCap> </rollingPolicy> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss.SSS},[%X{TraceId}] [%thread] %-5level %logger{36} %method:%L - %msg %n </pattern> </encoder> </appender> <!-- ERROR --> <appender name="common-error" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${error_log}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${error_log}.%d{yyyy-MM-dd}</fileNamePattern> <maxHistory>60</maxHistory> <totalSizeCap>10GB</totalSizeCap> </rollingPolicy> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss.SSS},[%X{TraceId}] [%thread] %-5level %logger{36} %method:%L - %msg%n%exception </pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <logger name="com.xxx" level="DEBUG"/> <!-- ROOT --> <root level="INFO"> <appender-ref ref="default"/> <appender-ref ref="common-error"/> <appender-ref ref="console"/> </root> </configuration>
-
api项目(CONSUMER&PROVIDER)
- com.alibaba.dubbo.rpc.Filter 文件,路径在src\main\resources\META-INF\dubbo目录下面,文件内容就是对应项目中指定的过滤器类路径
-
globalTraceFilter=com.xxx.filter.GlobalTraceFilter
-
- com.alibaba.dubbo.rpc.Filter 文件,路径在src\main\resources\META-INF\dubbo目录下面,文件内容就是对应项目中指定的过滤器类路径
-
-
GlobalTraceFilter类里面代码如下,实现dubbo的Filter接口:
package com.xxx.filter; import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.*; import org.slf4j.MDC; import java.util.UUID; /** * @Description 过滤器传递tradeId(消费者和生产者) * @Version v1.0 */ @Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER}) public class GlobalTraceFilter implements Filter { private static final String TRACE_ID = "TraceId"; @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { RpcContext rpcContext = RpcContext.getContext(); String traceId; if (rpcContext.isConsumerSide()) { traceId = MDC.get(TRACE_ID); if (traceId == null) { traceId = UUID.randomUUID().toString(); } rpcContext.setAttachment(TRACE_ID, traceId); }else if (rpcContext.isProviderSide()) { traceId = rpcContext.getAttachment(TRACE_ID); if (traceId == null) { traceId = UUID.randomUUID().toString(); } MDC.put(TRACE_ID, traceId); } return invoker.invoke(invocation); } }
-
logback-spring.xml配置和上面一样
3.traceId重复的处理
通过AOP进行处理,controller方法执行完后清除MDC,避免日志线程池中的线程复用导致MDC中traceId还存在则不会生成新的traceId;
web项目中RequestTraceIdAspect类代码如下:
package com.xxx.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
/**
* @Description
* @Create 2023/8/31
*/
@Aspect
@Component
public class RequestTraceIdAspect {
@After("execution(public * com.xxx.controller..*.*(..)) && !execution(* com.xxx.controller.BaseController.*(..))")
public void afterRequest() {
// 在请求结束时执行的逻辑,清空MDC中的TraceId,避免线程池中因线程复用导致上次请求的TraceId在后续请求中重复使用
MDC.clear();
}
}
特别说明一下 !execution(* com.xxx.controller.BaseController.*(..))排查BaseController中的方法,因为这里项目中Controller有继承BaseController做通用处理,会先调用里面的方法,如果在里面就清空了MDC,再到对应Controller执行真正业务逻辑的时候就没有traceId了;如果没有这种继承关系就不需要这段了
4.mybatis日志未记录到日志中的处理
现象如下图,api项目中sql日志并未显示traceId
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis日志标准输出改为如下通过slf4j的日志实现输出即可
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl
希望能帮到各位小伙伴哦~