首页 > 编程语言 >Java 实现全链路日志跟踪唯一ID

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

时间:2024-03-06 10:12:38浏览次数:35  
标签:task Java MDC ID 线程 链路 日志 public

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("/**");
    }
}

原链接 https://blog.csdn.net/HX0326CSDN/article/details/121281618

 

标签:task,Java,MDC,ID,线程,链路,日志,public
From: https://www.cnblogs.com/yk775879106/p/18055899

相关文章

  • Java 8 Supplier函数式接口介绍及代码样例
    介绍供应商接口(SupplierInterface)是Java8引入的java.util.function包的一部分,用于在Java中实现函数式编程。它表示一个函数,该函数不接收任何参数,但会产生一个类型为T的值。T:表示结果的类型分配给Supplier类型对象的lambda表达式用于定义其get(),最终产生一个值。......
  • Reference management in Java and Rust, and, how faster Rust can be?
    Hi,thisisablogcomparingthereferenceinrustandjava.IreallylovejavaandIhavespendsometimelearningtheframeworklikespringandothers.AftertakingCOMP6991,Ihavegotthisthink:Howjavamanagethereferenceinmyprogram?WhycanI......
  • Windows使用SIDCHG64 修改SID
    1、网站下载工具:https://www.stratesave.com/html/sidchg.html2、执行命令:sidchg64-3.0j.exe/KEY="************"/F/R注意:(1)sidchg有2个版本,标准版sidchg64和轻量版sidchgl64轻量版无需关闭defender实时防护,但是sid改得不彻底标准版执行之前需要关闭defender实时......
  • 雪花ID
    Twitter的分布式雪花算法SnowFlake,经测试每秒能够产生26万个自增可排序ID。twitter的SnowFlake生成ID能够按照时间有序生成;SnowFlake算法生成id的结果是一个64bit大小的整数,为一个Long型(转换成字符串后长度为19);分布式系统内不会产生ID碰撞(由datacenter和workerId作区......
  • java.util.Arrays 快速学习教程
    在Java中,java.util.Arrays类提供的多种数组操作功能,可以有效地执行各种数组相关的操作,使得数组处理变得简单和高效。打印数组String[]arr=newString[]{"a","b","c","d"};System.out.println(Arrays.toString(arr));//输出[a,b,c,d]Arrays.toString(arr),不过......
  • Java核心内容面试题详解
    前言随着经济的复苏,市场逐渐回暖,曾经的金三银四,金九银十也慢慢回归,在这个节骨眼上,我们要努力学习,做好知识储备,准备随时接收这泼天的offer。而我利用摸鱼(不是,是工作之余)时间也整理了一份关于Java核心知识的面试题,大家有兴趣,有需要的可以看看,希望能够给大家提供一些帮助Java基础面......
  • 关于Java并发多线程的一点思考
    写在开头在过去的2023年双11活动中,天猫的累计访问人次达到了8亿,京东超60个品牌销售破10亿,直播观看人数3.0亿人次,订单支付频率1分钟之内可达百万级峰值,这样的瞬间高并发活动,给服务端带来的冲击可想而知,就如同医院那么多医生,去看病挂号时,有时候都需要排队,对于很多时间就是金钱的场......
  • 【转】[Java]接口的 VO 使用内部类的写法
    参考:https://www.cnblogs.com/hyperionG/p/15602642.html以下代码段是向阿里的通义灵码提问得到的:importlombok.Data;@DatapublicclassOuterVO{//外部类的属性privateStringouterAttribute;//定义内部类并添加@Data注解@Datapublicst......
  • Java中的对象克隆
    对象克隆复制一个一模一样的新对象出来浅克隆拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)深克隆对象中基本类型的数据直接拷贝。对象中的字符串数据拷贝的还是地址。对象中包含的其他对象,不会拷贝地址,会创建新对象packagecom.aiit.itcq;imp......
  • Java进制之间的转换
    进制:我们生活中使用的是十进制计算机中使用的是二进制在Java中的进制的分类?十进制:逢十进一二进制:逢二进一八进制:逢八进一十六进制:逢十六进一10->A11->B12->C13->D14->E15->F在计算机中,数据......