common-trace模块:增加TraceIdFilter/FeignFillHeaderInterceptor

This commit is contained in:
xudawei 2024-04-23 15:43:39 +08:00
parent 2a5f7eb5bd
commit c0cb544667
7 changed files with 390 additions and 24 deletions

View File

@ -24,7 +24,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
@ -32,6 +32,16 @@
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,60 @@
package com.axzo.framework.trace.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author xudawei
* @date 2024/04/23
* @desc 调用前从 MDC中获取上一步骤设置的traceId放置到 header中供下一个服务从header中获取trace_id(注意接口服务方接收到的header里trace_id是小写的)
*/
@Configuration
@Slf4j
public class FeignFillHeaderInterceptor implements RequestInterceptor {
private static final String TRACE_ID = "traceId";
private static final String SPAN_ID = "spanId";
private static final String LOGIC_ID = "logicId";
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(TRACE_ID, MDC.get(TRACE_ID));
log.info("FeignFillHeaderInterceptor apply,traceId:{}", MDC.get(TRACE_ID));
requestTemplate.header(SPAN_ID, this.buildNextSpanId());
}
/**
* 下游的spanId:有本次spanId+logic组成
* logic默认从0开始每次调用下游时logic则自增1
*/
private String buildNextSpanId() {
//获取spanId
String spanId = this.fetchSpanId();
if (!StringUtils.hasText(MDC.get(LOGIC_ID))) {
MDC.put(LOGIC_ID, "0");
}
AtomicLong atomicLong = new AtomicLong(Long.parseLong(MDC.get(LOGIC_ID)));
Long nextSpanId = atomicLong.getAndIncrement();
MDC.put(LOGIC_ID, atomicLong.toString());
log.info("buildNextSpanId spanId:{}, nextSpanId:{}", spanId, nextSpanId);
return spanId + "." + nextSpanId;
}
/**
* 获取spanId
*/
private String fetchSpanId() {
String spanId = MDC.get(SPAN_ID);
if (!StringUtils.hasText(spanId)) {
MDC.put(SPAN_ID, "0");
}
return spanId;
}
}

View File

@ -1,23 +0,0 @@
package com.axzo.framework.trace.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
/**
* 调用前从 MDC中获取上一步骤设置的traceId放置到 header中供下一个服务从header中获取traceId
*/
@Configuration
public class FeignInterceptor implements RequestInterceptor {
private static final String TRACE_ID = "traceId";
public static final String CTX_LOG_ID_MDC = "ctxLogId";
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(TRACE_ID, MDC.get(TRACE_ID));
requestTemplate.header(CTX_LOG_ID_MDC, MDC.get(CTX_LOG_ID_MDC));
}
}

View File

@ -0,0 +1,100 @@
package com.axzo.framework.trace.interceptor;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.axzo.framework.trace.util.ExceptionUtil;
import com.axzo.framework.trace.wrapper.BodyReaderHttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* 简单的打印一些入参
*
* @author tanjie@axzo.cn
* @date 2021/12/13 11:13
*/
@Slf4j
@ConditionalOnProperty(prefix = "axzo.log.pokonya", name = "enable", havingValue = "true", matchIfMissing = true)
@Component
public class RequestLogHandlerInterceptor implements HandlerInterceptor, WebMvcConfigurer {
private static final String CHECK_DEATH_URL = "/checkDeath";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//filter check death
if (request.getRequestURI().contains(CHECK_DEATH_URL)) {
return true;
}
if (handler instanceof HandlerMethod) {
ExceptionUtil.ignoreException(() -> processRequestLog(request), (e) -> log.warn(e.getMessage(), e));
return true;
}
log.info("[requestUrl:{}]", request.getRequestURL());
return true;
}
private void processRequestLog(HttpServletRequest request) {
if (request.getDispatcherType().equals(DispatcherType.ERROR)) {
return;
}
if (!(request instanceof BodyReaderHttpServletRequestWrapper)) {
log.warn("print request log error : current request entity not support");
return;
}
String method = request.getMethod();
StringBuffer requestUrl = request.getRequestURL();
if ("get".equalsIgnoreCase(method)) {
log.info("[requestUrl:{}][method:{}][param:{}]", requestUrl, method, JSON.toJSONString(request.getParameterMap()));
return;
}
if ("post".equalsIgnoreCase(method)) {
try {
String body = IOUtils.toString(request.getInputStream(), Charset.defaultCharset());
boolean isJson = JSONUtil.isTypeJSON(body);
boolean shortLength = body.length() < 4000;
if (shortLength) {
body = isJson ? JSONUtil.toJsonStr(JSONUtil.isTypeJSONObject(body) ? JSON.parseObject(body) : JSON.parseArray(body)) : body;
log.info("[requestUrl:{}][method:{}][param:{}][body:{}]", requestUrl, method,
JSON.toJSONString(request.getParameterMap()), body);
return;
} else {
log.warn("request body too long, ignore print");
}
log.info("requestUrl:{}", requestUrl);
} catch (IOException ioException) {
log.warn("打印参数失败[requestUrl:{}]", requestUrl, ioException);
}
return;
}
//other method
log.info("requestUrl:{}", requestUrl);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this);
}
}

View File

@ -0,0 +1,96 @@
package com.axzo.framework.trace.interceptor;
import cn.hutool.core.util.StrUtil;
import com.axzo.framework.trace.util.ExceptionUtil;
import com.axzo.framework.trace.wrapper.BodyReaderHttpServletRequestWrapper;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
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;
/**
* @author TanJ
* @date 2021/5/24
*/
@Slf4j
@ConditionalOnProperty(prefix = "axzo.log.pokonya", name = "enable", havingValue = "true", matchIfMissing = true)
@Component
@Order(-99999)
public class TraceIdFilter extends OncePerRequestFilter {
private static final String TRACE_ID = "traceId";
private static final String CTX_LOG_ID = "ctxLogId";
private static final String X_REQUEST_ID = "x-request-id";
private static final String SPAN_ID = "spanId";
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
// wrapper
BodyReaderHttpServletRequestWrapper bodyRequest = new BodyReaderHttpServletRequestWrapper(
request);
// trace id 补充
ExceptionUtil.ignoreException(() -> setTraceId(bodyRequest, response), null);
//do
try {
filterChain.doFilter(bodyRequest, response);
} finally {
MDC.clear();
}
}
private void setTraceId(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response) {
String traceId = this.fetchTraceId(request);
log.info("TraceIdFilter setTraceId,traceId:{}", traceId);
//set
MDC.put(TRACE_ID, traceId);
MDC.put(CTX_LOG_ID, traceId);
MDC.put(SPAN_ID, this.fetchSpanId(request));
response.setHeader(TRACE_ID, traceId);
}
/**
* 获取traceId
*/
private String fetchTraceId(HttpServletRequest request) {
//header: ctxLogId -> traceId -> x-request-id
String traceId = StrUtil.blankToDefault(request.getHeader(CTX_LOG_ID),
StrUtil.blankToDefault(request.getHeader(TRACE_ID),
request.getHeader(X_REQUEST_ID)));
// blank to new
return StrUtil.blankToDefault(traceId, UUID.randomUUID().toString().replaceAll("-", ""));
}
/**
* 获取spanId
*/
private String fetchSpanId(HttpServletRequest request) {
// blank to new
String spanId = StrUtil.blankToDefault(request.getHeader(SPAN_ID), "0");
log.info("TraceIdFilter fetchSpanId,spanId:{}", spanId);
return spanId;
}
}

View File

@ -0,0 +1,28 @@
package com.axzo.framework.trace.util;
import java.util.function.Consumer;
/**
* @author tanjie@axzo.cn
* @date 2021/12/13 14:10
*/
public class ExceptionUtil {
/**
* 忽略错误
* @param supplier
* @param catchConsumer
*/
public static void ignoreException(Runnable supplier, Consumer<Exception> catchConsumer) {
try {
supplier.run();
} catch (Exception e) {
if (null != catchConsumer) {
catchConsumer.accept(e);
}
}
}
}

View File

@ -0,0 +1,95 @@
package com.axzo.framework.trace.wrapper;
import lombok.SneakyThrows;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Collection;
/**
* 自定义request
*
* @author TanJ
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private final HttpServletRequest request;
@SneakyThrows
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.request = request;
// request.getParameterMap();
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
return request.getParts();
}
private String getBodyString(ServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream ins = request.getInputStream();
try (BufferedReader isr = new BufferedReader(
new InputStreamReader(ins, Charset.forName("UTF-8")));) {
String line = "";
while ((line = isr.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw e;
}
return sb.toString();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return bais.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}