package com.csw.mysqldate.interceptor.traceId;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
template.header("requestId", TraceIdFilter.getRequestId());
// Enumeration<String> headerNames = request.getHeaderNames();
// while (headerNames.hasMoreElements()) {
// String headerName = headerNames.nextElement();
// String headerValue = request.getHeader(headerName);
// template.header(headerName, headerValue);
// }
}
}
};
}
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] [traceId:%X{traceId}] - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.csw" level="info"/>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
package com.csw.mysqldate.interceptor.traceId;
import com.alibaba.fastjson.JSON;
import com.csw.mysqldate.result.Result;
import com.csw.mysqldate.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Slf4j
@Aspect
@Component
public class MarkInterceptor {
static String package_name = "com.csw.";
//要扫描的方法必须是public
@Around("execution(* com.csw..*Controller.*(..))")
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
Object[] args = joinPoint.getArgs();
//主方法从Attribute里拿,从方法从Header拿
String requestId = TraceIdFilter.getRequestId();
log.info("请求路径:{},【开始】", request.getServletPath());
if (args != null) {
for (Object arg : args) {
if (isCustrom(arg)) {
log.info("请求路径:{},【入参】:{}", request.getServletPath(), JSON.toJSONString(arg));
}
}
}
long begin = System.nanoTime();
Object result = joinPoint.proceed(args);
long end = System.nanoTime();
log.info("请求路径:{},【耗时】:{}毫秒", request.getServletPath(), (end - begin) / 1000000);
if (result instanceof Result) {
((Result<?>) result).setRequestId(requestId);
}
if (isCustrom(result)) {
log.info(";请求路径:{},【出参】:{}", request.getServletPath(), JSON.toJSONString(result));
}
log.info("请求路径:{},【结束】", request.getServletPath());
return result;
}
//要扫描的方法必须是public
@Around("execution(* com.csw..GlobalExceptionHandler.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
Object object = pjp.proceed();
String requestId = TraceIdFilter.getRequestId();
if (requestId == null) {
requestId = "未进入方法报错";
}
if (object instanceof Result) {//Result是返回的封装对象,具体可以看我其他博客,里面多一个参数requestId
((Result<?>) object).setRequestId(requestId);
}
log.info("请求路径:{},【结束】", request.getServletPath());
return object;
}
private static boolean isCustrom(Object arg) {
if (arg == null) {
return false;
}
String path = arg.getClass().getName();
//其他的可以自己补充
if (path.startsWith(package_name)) {
return true;
} else if (path.startsWith("java.lang.String") ||
path.startsWith("java.lang.Integer") ||
path.startsWith("java.lang.Long") ||
path.startsWith("java.util.List") ||
path.startsWith("java.util.ArrayList") ||
path.startsWith("java.util.Map")) {
return true;
} else if (path.startsWith("com.alibaba.fastjson.JSONObject") ||
path.startsWith("com.google.gson.Gson")) {//json对象
return true;
}
if (path.startsWith("java.io.")) {
return false;
}
return false;
}
/**
* 声明环绕通知
*
* @param pjp
* @return
* @throws Throwable
*/
//要扫描的方法必须是public
@Around("execution(* com.csw..*Dao.*(..))||execution(* com.csw..*Mapper.*(..))")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//主方法从Attribute里拿,从方法从Header拿
String requestId = TraceIdFilter.getRequestId();
long begin = System.nanoTime();
Object obj = pjp.proceed();
long end = System.nanoTime();
log.info("请求路径:{},调用Mapper方法:{},参数:{},执行耗时:{}纳秒,耗时:{}毫秒", request.getServletPath(), pjp.getSignature().toString(), Arrays.toString(pjp.getArgs()),
(end - begin), (end - begin) / 1000000);
return obj;
}
}
package com.csw.mysqldate.interceptor.traceId;
import cn.hutool.core.util.IdUtil;
import com.csw.mysqldate.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 在启动类上要加注解
*
* @ServletComponentScan
*/
@Slf4j
@WebFilter(filterName = "TraceIdFilter", urlPatterns = "/*")
public class TraceIdFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
if (request.getAttribute("requestId") == null && request.getHeader("requestId") == null) {
//进来的是主方法
log.info("【进的是主方法】");
request.setAttribute("requestId", SnowflaketoBase62Id());
}
// 生成traceId并放入MDC
MDC.put("traceId", TraceIdFilter.getRequestId());
log.info("【TraceIdFilter进来了】");
try {
// 继续执行请求链
filterChain.doFilter(servletRequest, servletResponse);
} finally {
// 请求结束后清除MDC中的值
MDC.clear();
}
log.info("【TraceIdFilter结束了】");
}
@Override
public void destroy() {
}
private static final char[] BASE_62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
public static String SnowflaketoBase62Id() {
Long snowflakeId = IdUtil.getSnowflake().nextId();
StringBuilder base62 = new StringBuilder();
do {
int remainder = (int) (snowflakeId % 62);
base62.insert(0, BASE_62_CHARS[remainder]);
snowflakeId = snowflakeId / 62;
} while (snowflakeId > 0);
return base62.toString();
}
public static String getRequestId() {
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
String requestId;
if (request.getAttribute("requestId") != null) {
requestId = request.getAttribute("requestId").toString();
} else if (request.getHeader("requestId") != null) {
requestId = request.getHeader("requestId").toString();
} else {
requestId = null;
}
return requestId;
}
}
如果嵌套了多线程需要手动指定
int aa = 10;
String requestId = TraceIdFilter.getRequestId();
for (int i = 0; i < aa; i++) {
CompletableFuture future = CompletableFuture.runAsync(() -> {
MDC.put("traceId", requestId);
log.info("aa");
});
future.get();
}
经过并发测试是没有问题的MDC不会被覆盖
或
并发测试方法https://www.jianshu.com/p/22dc499ef1f8
异常类https://www.jianshu.com/p/d395aef20c15