基本实现原理:
- 过滤所有请求:有Request-No请求头则获取请求号,没有请求头则设置请求头
- feign远程调用添加过滤器,自动赋值请求头
- 以上两步能够完成当前线程与子线程 参数传递,对于线程池则无能为力。对于线程池则需要手动设置于释放
对于不携带Request-No的请求,则生成并添加请求头,添加请求头需要包装请求对象
package com.aimilin.common.security.filter;
import com.aimilin.common.core.consts.CommonConstant;
import com.aimilin.common.core.context.requestno.RequestNoContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
public class RequestNoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
// 生成唯一请求号uuid
String requestNo = this.getRequestNo(request);
request = this.setRequestHeader(request, requestNo);
// 增加响应头的请求号
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.addHeader(CommonConstant.REQUEST_NO_HEADER_NAME, requestNo);
// 临时存储
RequestNoContext.set(requestNo);
MDC.put(CommonConstant.TRACE_ID, requestNo);
// 放开请求
chain.doFilter(request, response);
} finally {
// 清除临时存储的唯一编号
RequestNoContext.clear();
MDC.remove(CommonConstant.TRACE_ID);
}
}
private ServletRequest setRequestHeader(ServletRequest request, String requestNo) {
// 没有包含请求号,则将请求号添加到请求头中
if(request instanceof HttpServletRequest && StringUtils.isBlank(((HttpServletRequest) request).getHeader(CommonConstant.REQUEST_NO_HEADER_NAME))){
HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper((HttpServletRequest) request);
requestWrapper.addHeader(CommonConstant.REQUEST_NO_HEADER_NAME, requestNo);
return requestWrapper;
}
return request;
}
private String getRequestNo(ServletRequest request){
if(request instanceof HttpServletRequest){
String requestNo = ((HttpServletRequest) request).getHeader(CommonConstant.REQUEST_NO_HEADER_NAME);
if(StringUtils.isNotBlank(requestNo)){
return requestNo;
}
}
return UUID.randomUUID().toString();
}
@Override
public void destroy() {
}
}
包装请求对象:
package com.aimilin.common.security.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;
public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
public HeaderMapRequestWrapper(HttpServletRequest request) {
super(request);
}
private Map headerMap = new HashMap();
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
@Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
@Override
public Enumeration getHeaderNames() {
List names = Collections.list(super.getHeaderNames());
names.addAll(headerMap.keySet());
return Collections.enumeration(names);
}
@Override
public Enumeration getHeaders(String name) {
List values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
}
经过请求头过滤器那么所有请求都会携带上Request-No请求头, 响应也会携带上Request-No
2. Feigin远程调用,自动复制请求头package com.aimilin.common.feign.inteceptor;
import com.aimilin.common.core.utils.JsonUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Objects;
import java.util.Optional;
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration headerNames = request.getHeaderNames();
Optional.ofNullable(headerNames).ifPresent(headers -> {
while (headers.hasMoreElements()) {
String name = headers.nextElement();
String value = request.getHeader(name);
// 跳过 content-length
if (name.equals("content-length")){
continue;
}
requestTemplate.header(name, value);
log.trace(">>> feign header set {}:{}", name, value);
}
});
}
printLog(requestTemplate);
}
private void printLog(RequestTemplate requestTemplate) {
if (log.isInfoEnabled()) {
Target> target = requestTemplate.feignTarget();
log.info("Feign Request:n app: {}n class: {}n method: {}n url: {}n param: {}n",
target.name(), target.type().getName(), requestTemplate.method(),
requestTemplate.url(),
this.getParam(requestTemplate)
);
}
}
private String getParam(RequestTemplate requestTemplate) {
if (RequestMethod.GET.name().equals(requestTemplate.method())) {
return JsonUtil.toJson(requestTemplate.queries());
}
return Objects.isNull(requestTemplate.body()) ? "" : new String(requestTemplate.body());
}
}
对于线程池中执行的任务还是不能携带MDC和请求对象,因为RequestContextHolder也只能在当前线程与子线程中使用Request对象;
3. 线程池中使用MDC与Request对象其原理就是任务执行前复制好变量,结束之后再删除变量。这里使用了alibaba TransmittableThreadLocal 线程池支持库;
import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import lombok.NonNull;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import static java.util.Optional.ofNullable;
public class TtlThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
@NonNull
@Override
public void execute(@NonNull Runnable task) {
super.execute(wrap(task));
}
@NonNull
@Override
public Future> submit(@NonNull Runnable task) {
return super.submit(wrap(task));
}
@NonNull
@Override
public Future submit(@NonNull Callable task) {
return super.submit(wrap(task));
}
@NonNull
@Override
public ListenableFuture> submitListenable(@NonNull Runnable task) {
return super.submitListenable(wrap(task));
}
@NonNull
@Override
public ListenableFuture submitListenable(@NonNull Callable task) {
return super.submitListenable(wrap(task));
}
private Callable wrap(Callable task) {
Optional requestOptional = ofNullable(RequestContextHolder.getRequestAttributes());
Optional
异步现成初始化
@Lazy
@Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
ThreadPoolTaskExecutor threadPoolTaskExecutor = builder.configure(new TtlThreadPoolTaskExecutor());
// CALLER_RUNS:调用者所在的线程来执行
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return threadPoolTaskExecutor;
}
经过上面几部,基本完成链路日志追踪,接下来是设置MDC
4. 设置日志MDC添加配置:主要是设置控制台日志格式与文件日志格式。
使用方式:
参考:https://docs.spring.io/spring-boot/docs/2.6.x/reference/htmlsingle/#using
打印出来日志格式:



