栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

链路追踪 | MDC+SpringBoot自动装配实现链路追踪组件

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

链路追踪 | MDC+SpringBoot自动装配实现链路追踪组件

MDC+SpringBoot自动装配实现链路追踪组件
  • 一、前言
  • 二、项目
    • 1、项目结构
    • 2、sample-framework pom
    • 3、sample-trace-boot-starter pom 坐标
    • 4、TraceAutoConfiguration 自动装配配置类
    • 5、TraceProperties 配置变量
    • 6、MDCParam 自动装配配置类
    • 7、TraceConstants 常量类
    • 8、MDCUtil 工具类
    • 9、TraceLocalUtil 本地缓存
    • 10、RabbitTraceAspect
    • 11、HttpTraceIdFilter
    • 12、OpenFeignRequestInterceptor
    • 13、 resource
  • 三、测试
  • 四、总结

一、前言

        在项目中使用 Slf4j 的 MDC 机制 可以做到链路追踪,但是项目的微服务化,很多项目都需要进行链路追踪,所以通过 SpringBoot的自动装配机制 将链路追踪 封装为 一个组件,其他项目直接 POM 坐标引用就可以直接做到日志的链路追踪。

       在封装组件的过程中最好成立一个单独的父子工程组件项目,不单单可以封装链路追踪的组件,还可以后续封装其他组件。
例如:工具类的组件 相信大家在开发过程中会使用到很多公用的工具类 可以封装到组件项目中,大家可以参考一下我的这种方式

  • my-framework - 父项目
  • my-trace-boot-starter 链路追踪 (本次讲的)
  • my-commons 是我封装的一些工具类
  • 其他暂时忽略吧这是我个人玩着搞得

用到技术:

  • SpringBoot 自动装配
  • Slf4j MDC机制
  • 核心是日志中的 唯一id traceId 来做到日志追踪
二、项目 1、项目结构

        这里的代码也是以新建父子工程组件项目来演示公开的。

        这里代码有点多,如果不想费劲看的话可以直接拉到底部,直接git 将项目拉下来参考使用。

2、sample-framework pom


    
        sample-framework
        com.su
        1.0.0
    
    4.0.0

    sample-trace-boot-starter
    jar

    
        8
        8
    

    
        
            org.projectlombok
            lombok
        

        
            cn.hutool
            hutool-all
        

        
            org.apache.commons
            commons-lang3
        

        
            org.slf4j
            slf4j-api
        

        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

        
            org.springframework.boot
            spring-boot-starter-web
        

    



3、sample-trace-boot-starter pom 坐标


    
        sample-framework
        com.su
        1.0.0
    
    4.0.0

    sample-trace-boot-starter
    jar

    
        8
        8
    

    
        
            org.projectlombok
            lombok
        

        
            cn.hutool
            hutool-all
        

        
            org.apache.commons
            commons-lang3
        

        
            org.slf4j
            slf4j-api
        

        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

        
            org.springframework.boot
            spring-boot-starter-web
        

    



4、TraceAutoConfiguration 自动装配配置类
package com.sample.trace.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
//加载过滤器需要的注解
@ServletComponentScan(basePackages = "com.sample.trace")
@ComponentScan(basePackages = "com.sample.trace")
public class TraceAutoConfiguration {

    public TraceAutoConfiguration() {
        log.info("TraceAutoConfiguration---------create ok");
    }

}
5、TraceProperties 配置变量
package com.sample.trace.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component(value = "traceProperties")
@ConfigurationProperties(prefix = "zohe.trace")
public class TraceProperties {

    
    private String serverName="NO_NAME";

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }
}

6、MDCParam 自动装配配置类
package com.sample.trace.bean;

import lombok.Data;


@Data
public class MDCParam {

    
    private String traceId;

    
    private String upServer;

    
    private String serverName;

}

7、TraceConstants 常量类
package com.sample.trace.consts;


public interface TraceConstants {

    
    String TRACE_ID_KEY = "traceId";

    
    String UP_SERVER = "upServer";

    
    String SERVER_NAME = "serverName";
}

8、MDCUtil 工具类
package com.sample.trace.utils;

import cn.hutool.core.bean.BeanUtil;
import com.sample.trace.bean.MDCParam;
import org.slf4j.MDC;
import java.util.Map;

public class MDCUtil {

    public static void put(MDCParam param) {
        Map map = BeanUtil.beanToMap(param);
        TraceLocalUtil.setMap(map);
        //MDC.setContextMap(traceMap); 源码是,会存在覆盖问题 传入的Map 替换 原来MDC的中的map 决定使用put
        for (String key : map.keySet()) {
            if (map.get(key) != null) {
                MDC.put(key, map.get(key).toString());
            }
        }
    }

    public static void clear() {
        MDC.clear();
        TraceLocalUtil.clear();
    }
}

9、TraceLocalUtil 本地缓存
package com.sample.trace.utils;

import java.util.Map;

public class TraceLocalUtil {

    private static final ThreadLocal> traceIdTreadLocal = new ThreadLocal<>();

    public static void setMap(Map traceMap) {
        traceIdTreadLocal.set(traceMap);
    }

    public static Map getMap() {
        return traceIdTreadLocal.get();
    }

    public static void clear() {
        traceIdTreadLocal.remove();
    }
}

10、RabbitTraceAspect

RabbitMQ使用AOP 的方式 接受 MDC插入traceId

package com.sample.trace.aop;

import com.sample.trace.bean.MDCParam;
import com.sample.trace.config.TraceProperties;
import com.sample.trace.utils.MDCUtil;
import com.sample.trace.utils.TraceLocalUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;
import java.util.UUID;


@Aspect
@Component
@Order(0)
public class RabbitTraceAspect {

    @Resource
    private TraceProperties traceProperties;

    @Pointcut("@annotation(org.springframework.amqp.rabbit.annotation.RabbitListener)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            Map localMap = TraceLocalUtil.getMap();
            if (localMap == null) {
                MDCUtil.clear();
                MDCParam param = new MDCParam();
                param.setTraceId(UUID.randomUUID().toString());
                param.setServerName(traceProperties.getServerName());
                //TODO upServer
                param.setUpServer(traceProperties.getServerName());
                MDCUtil.put(param);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Object result = joinPoint.proceed();
        // 清除TreadLocal MDC
        MDCUtil.clear();
        return result;
    }

}


11、HttpTraceIdFilter

http 请求 使用过滤器 来 插入 traceId

package com.sample.trace.filter;

import com.sample.trace.bean.MDCParam;
import com.sample.trace.config.TraceProperties;
import com.sample.trace.consts.TraceConstants;
import com.sample.trace.utils.MDCUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;

@Slf4j
@WebFilter(urlPatterns = "/*")
public class HttpTraceIdFilter implements Filter {

    public HttpTraceIdFilter() {
        log.info("HttpTraceIdFilter---------create ok");
    }

    private TraceProperties traceProperties;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //tomcat 容器启动 springboot 项目 关于servlet 的配置都会失效
        //filter 也是servlet 也是
        if (traceProperties == null) {
            WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(filterConfig.getServletContext());
            traceProperties = applicationContext.getBean(TraceProperties.class);
        }
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        //先清除本地缓存
        try {
            MDCUtil.clear();
            MDCParam param = new MDCParam();
            String traceId = httpRequest.getHeader(TraceConstants.TRACE_ID_KEY);
            String upServer = httpRequest.getHeader(TraceConstants.UP_SERVER)
            if (StringUtils.isBlank(traceId)) {
                traceId = UUID.randomUUID().toString();
            }
            param.setTraceId(traceId);
            if (StringUtils.isNotBlank(upServer)) {
                param.setUpServer(upServer);
            }
            param.setServerName(traceProperties.getServerName());
            MDCUtil.put(param);
        } catch (Exception e) {
            e.printStackTrace();
        }
        chain.doFilter(request, response);
    }

}

12、OpenFeignRequestInterceptor

这里使用OpenFegin 来作为交互组件 使用请求拦截器来传递 TraceId

package com.sample.trace.interce;

import com.sample.trace.consts.TraceConstants;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OpenFeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        String traceId = MDC.get(TraceConstants.TRACE_ID_KEY);
        String serverName = MDC.get(TraceConstants.SERVER_NAME);
        requestTemplate.header(TraceConstants.TRACE_ID_KEY, traceId);
        requestTemplate.header(TraceConstants.UP_SERVER, serverName);
    }
}

13、 resource

additional-spring-configuration-metadata.json

{
  "properties": [
    {
      "name": "zohe.trace.serverName",
      "type": "java.lang.String",
      "defaultValue": "NO_NAME",
      "description": "trace 当前服务名称"
    }
  ]
}

spring.factories 自动装配

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.sample.trace.config.TraceAutoConfiguration

spring-configuration-metadata.json

{
  "groups": [
    {
      "name": "server",
      "type": "org.springframework.boot.autoconfigure.web.ServerProperties",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
    }
  ],
  "properties": [
    {
      "name": "zohe.trace.server-name",
      "type": "java.lang.String",
      "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
    }
  ],
  "hints": [
    {
      "name": "spring.jpa.hibernate.ddl-auto",
      "values": [
        {
          "value": "none",
          "description": "Disable DDL handling."
        },
        {
          "value": "validate",
          "description": "Validate the schema, make no changes to the database."
        }
      ]
    }
  ]
}
三、测试

测试项目需要修改 logback.xml 加上 traceId 输出的位置

这里就简单截图一下测试项目 需要项目的可以看底部 最下边gitee 自己拉



    zohe-video-struct
    
    
    
    
        
    
        
            ${STDOUT_PATTERN}
        
    

    
    
        ${LOGPATH}/${APP_NAME}/${APP_NAME}.debug.log
        
            ${LOGPATH}/${APP_NAME}/${FILE_NAME}.debug.bak
            30
        
        true
        
            ${PATTERN}
        
        
            DEBUG
            ACCEPT
            DENY
        
    
    
        ${LOGPATH}/${APP_NAME}/${APP_NAME}.info.log
        
            ${LOGPATH}/${APP_NAME}/${FILE_NAME}.info.bak
            30
        
        true
        
            ${PATTERN}
        
        
            INFO
            ACCEPT
            DENY
        
    
    
        ${LOGPATH}/${APP_NAME}/${APP_NAME}.error.log
        
            ${LOGPATH}/${APP_NAME}/${FILE_NAME}.error.bak
            30
        
        true
        
            ${PATTERN}
        
        
            ERROR
            ACCEPT
            DENY
        
    
    
        ${LOGPATH}/${APP_NAME}/${APP_NAME}.warn.log
        
            ${LOGPATH}/${APP_NAME}/${FILE_NAME}.warn.bak
            30
        
        true
        
            ${PATTERN}
        
        
            WARN
            ACCEPT
            DENY
        
    

    
        
        0
        
        256
        
        
    

    
    
        
        
        
        
        
        
    

四、总结

项目gitee 地址: https://gitee.com/DianHaiShiYuDeMing/sample-framework

需要注意的事:

  • 不用MQ 需要将MQ 的AOP 注释掉
  • 不用openfeign 需要将 openfeign的请求拦截器注释掉
  • 需要修改logback.xml 的输出
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1013085.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号