分布式 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
欢迎点赞关注和收藏
强者自救 圣者渡人