分布式 trace id 实现

原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/14794027.html

 

Background

在分布式环境下,请求要在多个服务之间进行调用,服务集群每天接收上百万个请求,一旦某个用户请求发生异常,如何快速定位查询到这个请求。

一般的做法是:给同一个请求打上一个相同的标记。这样,只要拿到这个标记就可以查询到这个请求链路上所有的入参、出参、以及耗时,通常把这个标记叫做 requestId。我们可以采用Logback MDC、Servlet Filter 和 Spring AOP 的方式,完成项目通用分布式 Trace 的实现。

 

Project Directory

 

Maven Dependency

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.fool</groupId>
    <artifactId>springboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.10.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

application.properties

server.port=8080

spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true

logging.level.org.fool.springboot=debug

 

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="30 seconds">
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>utf-8</charset>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{requestId}] [%thread] %logger{5} - %m%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

Note: %X{requestId}

 

MonitorAspect.java

package org.fool.springboot.aspect;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.fool.springboot.util.JsonUtils;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
public class MonitorAspect {

    @Pointcut("execution(public * org.fool.springboot.controller..*.*(..))")
    public void controllerLog() {
    }

    @Pointcut("execution(public * org.fool.springboot.service..*.*(..))")
    public void serviceLog() {
    }


    @Around("controllerLog() || serviceLog()")
    public Object handle(ProceedingJoinPoint pjp) throws Throwable {
        String declaringTypeName = pjp.getSignature().getDeclaringTypeName();
        String methodName = pjp.getSignature().getName();
        String inputParamNames = JsonUtils.objectToJson(((MethodSignature) pjp.getSignature()).getParameterNames());
        String inputParamValues = JsonUtils.objectToJson(pjp.getArgs());

        long startTime = System.currentTimeMillis();
        log.info("[{}][{}] input param names: {}", declaringTypeName, methodName, inputParamNames);
        log.info("[{}][{}] input param values: {}", declaringTypeName, methodName, inputParamValues);
        log.info("[{}][{}] invoke start time: {}", declaringTypeName, methodName, startTime);

        try {
            Object result = pjp.proceed();
            log.info("[{}][{}] invoke result: {}", declaringTypeName, methodName, JsonUtils.objectToJson(result));
            return result;
        } catch (Throwable t) {
            log.error("[{}][{}] invoke error: {}", declaringTypeName, methodName, ExceptionUtils.getStackTrace(t));
            throw t;
        } finally {
            long endTime = System.currentTimeMillis();
            log.info("[{}][{}] invoke end time: {}, cost: {} ms", declaringTypeName, methodName, endTime, endTime - startTime);
        }
    }
}

Note: 打印输出入参、出参、耗时 

 

ApiFilter.java

package org.fool.springboot.filter;

import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

@Component
public class ApiFilter extends OncePerRequestFilter implements Ordered {

    public static final String REQUEST_ID_MDC_KEY = "requestId";

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        MDC.put(REQUEST_ID_MDC_KEY, UUID.randomUUID().toString());
        try {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } finally {
            MDC.remove(REQUEST_ID_MDC_KEY);
        }
    }
}

 

JsonUtils.java

package org.fool.springboot.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;
import java.util.Map;

public class JsonUtils {
    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static String objectToJson(Object data) {
        try {
            String string = MAPPER.writeValueAsString(data);
            return string;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
        try {
            T t = MAPPER.readValue(jsonData, beanType);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static <T> List<T> jsonToList(String jsonData, Class<T> beanType) {
        JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);

        try {
            List<T> list = MAPPER.readValue(jsonData, javaType);
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static <K, V> Map<K, V> jsonToMap(String jsonData, Class<K> keyType, Class<V> valueType) {
        JavaType javaType = MAPPER.getTypeFactory().constructMapType(Map.class, keyType, valueType);

        try {
            Map<K, V> map = MAPPER.readValue(jsonData, javaType);
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

 

TestService.java

package org.fool.springboot.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class TestService {
    public void test(String username, String password) {
        log.info("mock business service");
    }
}

 

TestController.java

package org.fool.springboot.controller;

import lombok.extern.slf4j.Slf4j;
import org.fool.springboot.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class TestController {
    @Autowired
    private TestService testService;

    @GetMapping(path = "/test")
    public String test() {
        testService.test("hello", "world");
        return "SUCCESS";
    }
}

 

Application.java

package org.fool.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

Run 

curl http://localhost:8080/test

 


欢迎点赞关注和收藏

 

posted @ 2021-05-21 15:55  李白与酒  阅读(222)  评论(0编辑  收藏  举报