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";@Overridepublic 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";@Overridepublic 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
希望能帮到各位小伙伴哦~