多线程中的ThreadLocal的传递问题以及LOG的MDC的使用
Log的MDC使用场景:
日志文件的RequsetId的打印,使用%X[RequestId]进行打印,但是接口层在发生异常的时候,会出现日志打印的%X[RequestId]的值为空的情况;
问题处理:
1、使用拦截器或者过滤器,增加MDC.put(RequestHeader.REQUEST_ID, values);
需要注意的是MDC引入的包是org.slf4j.MDC,log的文件使用log4j2.xml
import com.m.kit.common.constant.RequestHeader;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class MDCFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String values = httpRequest.getHeader("RequestId");
// key必须与log4j2.xml的配置文件的%X{}里的对应
MDC.put("RequestId", values);
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
2、如果你需要在多线程里日志打印也带上请求ID,则这个就与ThreadLocal有关,可以进行如下配置:在resource内增加log4j2.component.properties文件并增加如下配置
# 设置多线程的情况下,MDC也进行设置在子线程里面
isThreadContextMapInheritable=true
注意点:单这样配置还不完全行,倘如多线程使用的是线程池,你会发现,后续的多线程的日志打印的RequestId的值是不变的,因为线程池的线程是复用的,如需改变,则需要往下看
线程池的多线程的ThreadLocal等的传递问题
如需要进行传递到子线程,可以在线程池的配置中加入装饰器
注意:将Request在装饰器上进程传递的话,又可能出现子线程拿到的request信息为null的情况,这是由于request的生命周期的问题,所以不建议将request放进
mport java.util.Set;
public class ThreadLocalPermissionSet {
private ThreadLocalPermissionSet (){
}
private static final InheritableThreadLocal<Set<String>> PERMISSION_SET = new InheritableThreadLocal<>();
public static Set<String> getPermissionSet() {
return PERMISSION_SET.get();
}
public static void setPermissionSet(Set<String> permissionSet) {
PERMISSION_SET.set(permissionSet);
}
public static void removePermissionSet() {
PERMISSION_SET.remove();
}
}
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import java.util.Map;
public class MyTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 不建议使用Request的,因为生命周期问题
// RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
Set<String> permissionSet = ThreadLocalPermissionSet.getPermissionSet();
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// RequestContextHolder.setRequestAttributes(requestAttributes);
ThreadLocalPermissionSet.setPermissionSet(permissionSet );
MDC.setContextMap(contextMap);
runnable.run();
} finally {
// RequestContextHolder.resetRequestAttributes();
ThreadLocalPermissionSet.removePermissionSet();
MDC.clear();
}
};
}
}
@Bean(name = "myExecutorService")
protected Executor myExecutorService() {
ThreadPoolTaskExecutor myExecutor = new ThreadPoolTaskExecutor();
myExecutor.setCorePoolSize(8);
myExecutor.setMaxPoolSize(16);
myExecutor.setQueueCapacity(64);
myExecutor.setThreadNamePrefix("myExecutorService_");
myExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
myExecutor.setTaskDecorator(new MyTaskDecorator());
myExecutor.initialize();
return myExecutor ;
}
}
注意点:ThreadLocal是不可以继承的,如果需要传递ThreadLocal的值,可以使用其子类InheritableThreadLocal或者自己继承实现