MDC Overview:
MDC (Mapped Diagnostic Context) is a feature provided by LOG4J, LogBack, and Log4j2 to facilitate logging in multi-threaded conditions. MDC can be thought of as a hash table bound to the current thread to which you can add key-value pairs. The content contained in MDC can be accessed by code executing in the same thread. Children of the current thread inherit MDC content from their parent thread. To record logs, you only need to obtain the required information from the MDC. The MDC contents are saved by the program at the appropriate time. For a Web application, this data is usually saved at the beginning of the request being processed. Clear () => Remove all MDC nodes. Get (String key) => Get the value of the specified key in the CURRENT MDC thread. GetContext () => Get the MDC node of the current MDC thread. Remove (String key) => Delete the specified key/value pairs in the MDC of the current thread. Advantages: The code is simple and the log style is uniform. There is no need to manually spell traceId in the log print, that is, logger. info(“traceId:{} “, traceId)
MDC uses add interceptors
public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, String traceId = Request.getheader (Constants.TRACE_ID);if (traceId == null) {
traceId = TraceIdUtil.getTraceId();
}
MDC.put(Constants.TRACE_ID, traceId);
return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse Response, Object Handler, Exception ex) throws Exception {// Remove MDC.remove(Constants.TRACE_ID) after the call; }}Copy the code
Changing the Log Format
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
Copy the code
The important thing is that %X{traceId}, traceId and the key names in MDC are the same. Simple use is as easy as this, but in some cases traceId will not be available
Problems in the MDC Process Logs printed in sub-threads are lost traceId HTTP calls are lost traceId…… If the traceId is lost, solve the traceId problem one by one. Do not optimize the MDC process in advance. TraceId loss occurs in sub-thread logs
The traceId is lost when the child thread prints the log. The solution is to rewrite the thread pool, which is not used when creating a new thread. ThreadPoolExecutorMdcWrapper.java
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
returnsuper.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } @Override public Future<? > submit(Runnable task) {returnsuper.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); }}Copy the code
TraceId encapsulates the utility class threadMDcutil.java
public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(Constants.TRACE_ID) == null) {
MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return() - > {if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
returncallable.call(); } finally { MDC.clear(); }}; } public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return() - > {if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent(); try { runnable.run(); } finally { MDC.clear(); }}; }}Copy the code
[Take encapsulation Runnable as an example] : If the MDC Map of the current thread exists, set the traceId value of the MDC. If the Map does not exist, a new traceId value is generated. If the Map of the MDC thread is not null, run the following code, which is more intuitive
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return new Runnable() {
@Override
public void run() {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent(); try { runnable.run(); } finally { MDC.clear(); }}}; } ' 'returns the wrapped Runnable. Before this task is executed, the main thread Map is set to the current thread (mdC.setContextMap (context)). HTTP invocation Loss traceId The traceId will be lost when the third-party service interface is invoked using HTTP. You need to modify the HTTP invocation tool to add traceId to the Request header when sending the request. HttpClient, OKHttp, and RestTemplate are the most common types of HTTP calls. HttpClient is the most common type of HTTP calls. Implement the HttpClient interceptorCopy the code
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor { @Override public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { String traceId = MDC.get(Constants.TRACE_ID); If (traceId! = null) {// Add request body httprequest.addHeader (Constants.TRACE_ID, traceId); }}}
HttpRequestInterceptor rewrites the process method if the thread contains traceId. Add an interceptor to HttpClient. Add an interceptor to HttpClient using the addInterceptorFirst method:Copy the code
private static CloseableHttpClient httpClient = HttpClientBuilder.create() .addInterceptorFirst(new HttpClientTraceIdInterceptor()) .build();
Implement the OKHttp interceptorCopy the code
public class OkHttpTraceIdInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { String traceId = MDC.get(Constants.TRACE_ID); Request request = null; if (traceId ! = null) {// Add request body request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build(); } Response originResponse = chain.proceed(request);
return originResponse;
}
Copy the code
}
Implement the Interceptor Interceptor method, rewrite the Interceptor method, implement the same logic as HttpClient, if you can get the current thread traceId pass down to add interceptors for OkHttpCopy the code
private static OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new OkHttpTraceIdInterceptor()) .build();
Add the RestTemplate interceptor by calling the addNetworkInterceptor method: Implement the RestTemplate interceptorCopy the code
public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { String traceId = MDC.get(Constants.TRACE_ID); if (traceId ! = null) { httpRequest.getHeaders().add(Constants.TRACE_ID, traceId); }
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
Copy the code
}
Implement ClientHttpRequestInterceptor interface, and rewrite intercept method, explanation for the rest of the logic is the same not to repeat the RestTemplate add interceptorsCopy the code
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));
callsetInterceptors add an interceptor. Third-party service Interceptor: The whole process of invoking a third-party service interface through HTTP requires the cooperation of the third-party service. The third-party service needs to add an interceptor to obtain the traceId in the Request header and add it to the MDCCopy the code
public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, String traceId = Request.getheader (Constants.TRACE_ID); if (traceId == null) { traceId = TraceIdUtils.getTraceId(); }
MDC.put("traceId", traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
MDC.remove(Constants.TRACE_ID);
}
Copy the code
}
Description: Obtain the traceId from the Request header first. If the request header fails to obtain the traceId, it is not invoked by a third-party invocation. Directly generate a new traceId and store the generated traceId in the MDC. You also need to add traceId printing to the log format as follows:Copy the code
[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L – %msg%xEx%n
Need to add %X{traceId}Copy the code