基于logback实现日志链路追踪
1. MDC简介
MDC(Mapped Diagnostic Contexts)映射诊断上下文,它可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。然后我们利用这个键值对去赋予我们所需要的唯一链路ID,实现日志链路追踪。
2. MDC接入
2.1 配置logback.xml
核心就是配置:%X{traceId},基于此才可以完成日志中打印traceId。
<property name="log.pattern"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/>
2.2 基于拦截器实现(controller)
2.2.1 引入lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
2.2.2 新增拦截器
@Component
@Slf4j
public class RequestHeaderInterceptor implements HandlerInterceptor {
public static final String TRACE_ID = "traceId";
@Autowired
private RiskBlackListService riskBlackListService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = request.getHeader(TRACE_ID);
if (StringUtils.isEmpty(traceId)) {
traceId = UUID.randomUUID().toString();
}
MDC.put(TRACE_ID, traceId);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
MDC.remove(TRACE_ID);
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2.2.3 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RequestHeaderInterceptor requestHeaderInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestHeaderInterceptor).addPathPatterns("/**");
}
}
2.2.4 异步线程传递
异步线程传递traceId只需要将当前线程的MDC上下文传递进去即可,如下示例:
Map<String, String> previous = MDC.getCopyOfContextMap();
threadPoolExecutor.execute(() -> {
MDC.setContextMap(previous);
try {
log.info("test print log!")
} catch (Exception e) {
log.error("test print error log , error: ", e);
}
});
2.3 基于切面(全局)
新增全局切面,然后处理所有业务时写入traceId,在使用多线程时,将主线程的上下文传入到子线程内即可实traceId传递。
@Aspect
@Component
public class MdcTransferAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object transferMdc(ProceedingJoinPoint joinPoint) throws Throwable {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
try {
return joinPoint.proceed();
} finally {
if (contextMap != null) {
MDC.setContextMap(contextMap);
} else {
MDC.clear();
}
}
}
@Before("execution(* java.util.concurrent.Executor.execute(..))")
public void transferMdcToThreadPool(JoinPoint joinPoint) {
Runnable runnable = (Runnable) joinPoint.getArgs()[0];
Map<String, String> contextMap = MDC.getCopyOfContextMap();
if (contextMap != null) {
Runnable wrappedRunnable = () -> {
try {
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
if (joinPoint.getTarget() instanceof ThreadPoolExecutor) {
((ThreadPoolExecutor) joinPoint.getTarget()).execute(wrappedRunnable);
}
if (joinPoint.getTarget() instanceof ThreadPoolTaskExecutor) {
((ThreadPoolTaskExecutor) joinPoint.getTarget()).execute(wrappedRunnable);
}
// 或者其他线程池
}
}
@Around("@annotation(xxlJob)")
public Object handle(ProceedingJoinPoint point, XxlJob xxlJob) throws Throwable{
try{
MDC.put("traceId", XxlJobHelper.getLogId());
return point.proceed();
} finally{
MDC.remove("traceId");
}
}
}
2.4 基于自定义注解 + 切面(按需处理)
public interface TraceId {
public String traceId();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Traceable {
}
public abstract class MDCAwareRunnable implements Runnable {
private final Map<String, String> parentContext;
public MDCAwareRunnable() {
this.parentContext = MDC.getCopyOfContextMap();
}
@Override
public void run() {
if (null != parentContext) {
MDC.setContextMap(parentContext);
}
try {
runWithParentMDC();
} finally {
MDC.clear();
}
}
public abstract void runWithParentMDC();
}
@Aspect
@Component
public class TraceIdAspect {
private static final String TRACE_ID_KEY = "traceId";
@Around("@annotation(Traceable)")
public Object logTraceId(ProceedingJoinPoint joinPoint) throws Throwable {
TraceId traceId = getTraceIdArg(joinPoint.getArgs());
if (traceId == null) {
MDC.put(TRACE_ID_KEY, UUID.randomUUID().toString());
return joinPoint.proceed();
}
MDC.put(TRACE_ID_KEY, traceId.traceId());
try {
return joinPoint.proceed();
} finally {
MDC.clear();
}
}
private TraceId getTraceIdArg(Object[] args) {
for (Object arg : args) {
if (arg instanceof TraceId) {
return (TraceId) arg;
}
}
return null;
}
}
至此就可以实现服务间的链路追踪,方便日志的查看。