基于logback实现日志链路追踪

本文详细介绍了如何在Java应用中利用MDC(MappedDiagnosticContexts)和Logback实现日志链路追踪,包括配置logback.xml、使用控制器拦截器、全局切面以及自定义注解和切面的应用。这些方法有助于跟踪服务间请求,便于日志分析。
摘要由CSDN通过智能技术生成

基于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;
    }


}

至此就可以实现服务间的链路追踪,方便日志的查看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值