相关概念
traceId:用于标识某次请求的id
通过日志打印trace,能够快速定位日志中某次请求打印的所有日志,方便排查问题。
解决方案
通过配置拦截器+MDC实现,具体实现如下:
拦截器
import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TraceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
MDC.put("traceid", UUIDUtil.generateUuid());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
MDC.remove("traceid");
}
}
唯一id生成工具类
import java.util.UUID;
public class UUIDUtil {
public static String generateUuid() {
return UUID.randomUUID().toString().replace("-", "");
}
}
配置拦截器
@Configuration
public class InterceptorConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
logback配置
appender中日志打印格式中添加 %X{traceid},例如:
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread %level %c.%M:%L %X{traceid} - %msg%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
子线程情况
上面的配置无法将traceId 传递到子线程的日志中,如果想在子线程中打印traceId,需要使用自定义线程池,将线程的上下文传递到子线程中,线程池代码如下
public class MDCThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
/**
* @param corePoolSize
* @param maxPoolSize
* @param keepAliveTime
* @param queueCapacity
* @param poolNamePrefix
*/
public MDCThreadPoolTaskExecutor(int corePoolSize, int maxPoolSize,
int keepAliveTime, int queueCapacity, String poolNamePrefix) {
setCorePoolSize(corePoolSize);
setMaxPoolSize(maxPoolSize);
setKeepAliveSeconds(keepAliveTime);
setQueueCapacity(queueCapacity);
setThreadNamePrefix(poolNamePrefix);
}
public MDCThreadPoolTaskExecutor() {
super();
}
/**
* @return
*/
private Map<String, String> getContextForTask() {
return MDC.getCopyOfContextMap();
}
/**
* All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)
* all delegate to this.
*/
@Override
public void execute(Runnable command) {
super.execute(wrap(command, getContextForTask()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(wrap(task, getContextForTask()));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(wrap(task, getContextForTask()));
}
/**
* @param task
* @param context
* @param <T>
* @return
*/
private static <T> Callable<T> wrap(final Callable<T> task, final Map<String, String> context) {
return () -> {
if (context != null && !context.isEmpty()) {
MDC.setContextMap(context);
}
try {
return task.call();
} finally {
if (context != null && !context.isEmpty()) {
MDC.clear();
}
}
};
}
/**
* @param runnable
* @param context
* @return
*/
private static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return () -> {
if (context != null && !context.isEmpty()) {
MDC.setContextMap(context);
}
try {
runnable.run();
} finally {
if (context != null && !context.isEmpty()) {
MDC.clear();
}
}
};
}
}