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("請求路徑:{},【入?yún)ⅰ浚簕}", 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是返回的封裝對象,具體可以看我其他博客,里面多一個參數(shù)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;
}
/**
* 聲明環(huán)繞通知
*
* @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("請求路徑:{},調(diào)用Mapper方法:{},參數(shù):{},執(zhí)行耗時:{}納秒,耗時:{}毫秒", 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 {
// 繼續(xù)執(zhí)行請求鏈
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;
}
}

image.png
如果嵌套了多線程需要手動指定
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();
}

image.png
經(jīng)過并發(fā)測試是沒有問題的MDC不會被覆蓋

image.png
或

image.png
并發(fā)測試方法http://www.itdecent.cn/p/22dc499ef1f8
異常類http://www.itdecent.cn/p/d395aef20c15