转载自 Springboot使用MDC进行日志追踪_springboot日志追踪_繁华尽头满是殇的博客-CSDN博客
与之不同的是本文配置的是过滤器而不是拦截器
MDC(Mapped Diagnostic Context)是一个可以追踪程序上下文日志的东西,是springboot项目自带的org.slf4j
包下的类,无需引入额外依赖即可使用。
MDC
对用户 a 的日志进行跟踪。MDC
进行链路追踪。MDC
使用 ThreadLocal
存储日志数据,所以它是线程安全的,它可以把同一个请求的日志都存储一个相同的值,每次打印日志的时候,会自动打印出当前日志的键值value
,我们查询日志的时候就可以根据value
来查询。
此工具类定义了一个
traceId
作为日志的key
,使用UUID
为不同的请求生成不同的traceId
值,作为value
import org.slf4j.MDC;
import java.util.Map;
import java.util.UUID;
public class MdcUtil {
public static final String TRACE_ID = "traceId";
public static String generateTraceId() {
return UUID.randomUUID().toString().replace("-", "");
}
public static String getTraceId() {
return MDC.get(TRACE_ID);
}
public static void setTraceId(String traceId) {
MDC.put(TRACE_ID, traceId);
}
public static void setContextMap(Map<String, String> context) {
MDC.setContextMap(context);
}
public static void removeTraceId() {
MDC.remove(TRACE_ID);
}
public static void clear() {
MDC.clear();
}
}
下面是当请求进入时,为该请求生成一个traceId,如果有上层调用就用上层的ID,否则构建一个,上层ID就是微服务的时候,从消费者服务器请求过来时携带的traceId,这时应该沿用消费者服务器携带的ID作为,达到链路追踪的目的。
javax.servlet.http.HttpServletRequest
是无法修改 request 的 header 的,所以只能继承写个包装类,用Map类写个成员属性复制请求头。
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest;
private Map<String, String> headerMap = new HashMap<String, String>();
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
@Override
public Map<String,String[]> getParameterMap() {
Map<String,String[]> map = new LinkedHashMap<>();
Map<String,String[]> parameters = super.getParameterMap();
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
values[i] = xssEncode(values[i]);
}
map.put(key, values);
}
return map;
}
/**
* The default behavior of this method is to return getHeaderNames() on the * wrapped request object. */ @Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.addAll(headerMap.keySet());
return Collections.enumeration(names);
}
/**
* The default behavior of this method is to return getHeaders(String name) * on the wrapped request object. * * @param name
*/
@Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (StringUtils.isNotBlank(value)) {
value = xssEncode(value);
}
if (headerMap.containsKey(name)) {
value = headerMap.get(name);
}
return value;
}
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
private String xssEncode(String input) {
return XssUtils.filter(input);
}
/**
* 获取最原始的request
*/ public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取最原始的request
*/ public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
if (request instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
}
return request;
}
}
traceId
,有则用 将其放入 MDC 工具类中,无则自我生成一个。traceId
填充到其它的 Http 工具类中。traceId
public class XssFilter implements Filter {
@Override
public void init(FilterConfig config) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request);
String traceId = xssRequest.getHeader(MdcUtil.TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = MdcUtil.generateTraceId();
xssRequest.addHeader(MdcUtil.TRACE_ID, traceId);
}
MdcUtil.setTraceId(traceId);
GlobalHeaders.INSTANCE.header(MdcUtil.TRACE_ID, traceId);
// 其他的Http工具类
chain.doFilter(xssRequest, response);
}
@Override
public void destroy() {
MdcUtil.removeTraceId();
GlobalHeaders.INSTANCE.removeHeader(MdcUtil.TRACE_ID);
// 其他的Http工具类
}
}
配置过滤器为第一个过滤器
@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
需要重写线程池,在线程池启动新线程之前,复制当前线程的 traceId
/**
* 多线程日志追踪工具类
*
* @author fengxc
*/
public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MdcUtil.getTraceId() == null) {
MdcUtil.setTraceId(MdcUtil.generateTraceId());
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return () -> {
if (context == null) {
MdcUtil.clear();
} else {
MdcUtil.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MdcUtil.clear();
}
};
}
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return () -> {
if (context == null) {
MdcUtil.clear();
} else {
MdcUtil.setContextMap(context);
}
//设置traceId
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MdcUtil.clear();
}
};
}
}
/**
* 日志追踪线程池配置
*
* @author fengxc
*/
public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
@Override
public void execute(@NotNull Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@NotNull
@Override public Future<?> submit(@NotNull Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@NotNull
@Override public <T> Future<T> submit(@NotNull Callable<T> task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}
@SpringBootConfiguration
public class ThreadPoolConfig {
/**
* 这里定义一个日志追踪线程池,使用时直接注入ThreadPoolTaskExecutor,使用@Qualifier("getLogTraceExecutor")指定bean
*/ @Bean
public CustomThreadPoolTaskExecutor getLogTraceExecutor() {
//创建了一个ThreadPoolTaskExecutor的子类,在每次提交线程的时候都会做一些子类配置的操作
CustomThreadPoolTaskExecutor executor =new CustomThreadPoolTaskExecutor();
// 设置线程名称前缀
executor.setThreadNamePrefix("LogTraceExecutor-");
executor.initialize();
return executor;
}
/**
* 重写默认线程池配置,@Async异步会使用这个线程池
*/
@Bean
public Executor taskExecutor() {
//创建了一个ThreadPoolTaskExecutor的子类,在每次提交线程的时候都会做一些子类配置的操作
ThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();
// 设置线程名称前缀
executor.setThreadNamePrefix("logTranceAsyncPool-");
executor.initialize();
return executor;
}
}
注意:[%X{traceId}]
logging:
pattern:
console: ${CONSOLE_LOG_PATTERN:%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} [%X{traceId}] %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}