Java 实现全链路日志跟踪唯一ID

Java 实现全链路日志跟踪唯一ID

日志痛点:
使用Spring-Aop切面的时候,只能切控制层或者服务层的开始位置与结束位置的数据(也就是请求出入参),对于逻辑日志无法定位跟踪

普通打印日志的时候是这样子的

1.如果参数里面没有seq传递过来

LOGGER.error("xxx不能为空" );

2.参数里面有seq传递过来

LOGGER.error("【" + seq + "】,xxx不能为空" );

第一种更简洁,第二种入侵了业务逻辑,并且每次都要拼接

解决方案:
1.简单的配置(异步线程会有点问题,log4j日志)
1)这些配置放到前置拦截器里面即可,控制层一进来就会赋值

//前置拦截器
String logUid = UUID.randomUUID().toString();
//org.apache.logging.log4j.ThreadContext
ThreadContext.put("logId", logId);

//后置拦截器
//在请求结束时需要清理logId
ThreadContext.clearMap();

2)日志打印关键 [logId::%X{logId}]
xml配置版

 <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="[logId::%X{logId}][%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"/>
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
</console>

springboot版

logging:
  pattern:
    #配置日志全链路跟踪 logId
    console: "[logId::%X{logId}] [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%p] - %l - %m%n"

2.跟踪全链路,包括异步线程(logback日志)
关键点
1).MDC (org.slf4j.MDC)
2).拦截器 (主要是插入logId)
3).线程处理

拦截器代码,生成唯一logId

@Component
public class LogInterceptor extends HandlerInterceptorAdapter {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //如果有上层调用就用上层的ID
        String traceId = request.getHeader(LogConstant.TRACE_ID);
        if (traceId == null) {
    
    
            traceId = TraceIdUtil.getTraceId();
        }

        MDC.put(LogConstant.TRACE_ID, traceId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        //调用结束后删除
        MDC.remove(LogConstant.TRACE_ID);
    }
}

获取日志链路ID工具类(利用UUID生成序列号)

public class TraceIdUtil {
    
    

    private TraceIdUtil() {
    
    
        throw new UnsupportedOperationException("Utility class");
    }

    /**
     * 获取traceId
     * @return
     */
    public static String getTraceId() {
    
    
        return UUID.randomUUID().toString().replace("-", "").toUpperCase();
    }


}

日志常量(常量,不可以new , 其实可以使用接口类定义)

public class LogConstant {
    
    
    private LogConstant(){
    
    
        throw new UnsupportedOperationException();
    }

    /**
     * 日志追踪ID
     */
    public static final String TRACE_ID = "traceId";
}

mdc线程处理器

public class ThreadMdcUtil {
    
    

    public static void setTraceIdIfAbsent() {
    
    
        if (MDC.get(LogConstant.TRACE_ID) == null) {
    
    
            //插入唯一日志ID
            MDC.put(LogConstant.TRACE_ID, TraceIdUtil.getTraceId());
        }
    }

    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
    
    
        return () -> {
    
    
            if (context == null) {
    
    
                MDC.clear();
            } else {
    
    
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
    
    
                return callable.call();
            } finally {
    
    
                MDC.clear();
            }
        };
    }

    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
    
    
        return () -> {
    
    
            if (context == null) {
    
    
                MDC.clear();
            } else {
    
    
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
    
    
                runnable.run();
            } finally {
    
    
                MDC.clear();
            }
        };
    }

}

线程配置类

@Slf4j
@Configuration
public class ExecutorConfig {
    
    

    @Bean
    @Primary
    public Executor asyncServiceExecutor() {
    
    
        log.info("start asyncServiceExecutor");
        ThreadPoolExecutorMdcWrapper executor = new ThreadPoolExecutorMdcWrapper();
        //配置核心线程数
        executor.setCorePoolSize(10);
        //配置最大线程数
        executor.setMaxPoolSize(200);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-");

        // 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }


}

线程池配置

@Slf4j
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolTaskExecutor {
    
    

    private void showThreadPoolInfo(String prefix){
    
    
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if(null==threadPoolExecutor){
    
    
            return;
        }

        log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
    
    
        showThreadPoolInfo("1. do execute");
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));

    }

    @Override
    public void execute(Runnable task, long startTimeout) {
    
    
        showThreadPoolInfo("2. do execute");
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), startTimeout);

    }

    @Override
    public Future<?> submit(Runnable task) {
    
    
        showThreadPoolInfo("1. do submit");
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
    
    
        showThreadPoolInfo("2. do submit");
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
    
    
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
    
    
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

}

拦截器

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    
    

	@Autowired
	private PreventRepeatSubmitInterceptor preventRepeatSubmitInterceptor;

	@Autowired
	private LogInterceptor logInterceptor;
	
    @Override
    public void addCorsMappings(CorsRegistry registry) {
    
    
        //设置允许跨域的路径
    	registry.addMapping("/**") //映射地址
		.allowedOrigins("*")//允许跨域地址
		.allowedHeaders("*")
		.allowCredentials(true)
	    .allowedMethods("GET", "POST")
	    .maxAge(3600);
    }


	@Override
	public void addInterceptors(InterceptorRegistry registry) {
    
    
    	//.excludePathPatterns("/wechatwork/**")  .addPathPatterns("/order/**")
		//防重复提交拦截器
		registry.addInterceptor(preventRepeatSubmitInterceptor);
		//日志拦截器
		registry.addInterceptor(logInterceptor);//.addPathPatterns("/**");
	}



}

最终效果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/HX0326CSDN/article/details/121281618