Reprint: How to implement full link call log tracing in SpringBoot? This method is elegant!
code: https://github.com/Yong-Wang-Brave/springboot-log-master
Author: is he Tiantian there
Source: Juejin cn/post/6844904101483020295
Write in front
Through this article, we will understand what MDC is, the problems existing in MDC application and how to solve the existing problems
Introduction to MDC
brief introduction
MDC (Mapped Diagnostic Context) is a function provided by log4j, logback and log4j2 to facilitate logging under multi-threaded conditions. MDC # can be regarded as a hash table bound to the current thread to which key value pairs can be added. The content contained in MDC can be accessed by code executed in the same thread. The child thread of the current thread will inherit the contents of the MDC in its parent thread. When logging is required, you only need to obtain the required information from MDC. The content of MDC is saved by the program at an appropriate time. For a Web application, this data is usually saved at the beginning of the request being processed
API description
-
Clear() = > remove all MDC s
-
Get (string key) = > get the value of the specified key in the MDC of the current thread
-
Getcontext() = > get the MDC of the current thread
-
Put (string key, object o) = > store the specified key value pair into the MDC of the current thread
-
Remove (string key) = > delete the key value pair specified in the MDC of the current thread
advantage:
-
The code is concise and the log style is unified. There is no need to spell traceid manually in log printing, that is, logger info("traceId:{} ", traceId)
I can only think of this for the time being
MDC usage
-
Add interceptor
public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //If there is an upper layer call, use the upper layer ID 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 { //Delete after call MDC.remove(Constants.TRACE_ID); } }
-
Modify log format
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
The key point is% X{traceId}. traceId is consistent with the key name in MDC. It is so easy to use, but in some cases, traceId will not be obtained
Problems in MDC
-
traceId is missing from the print log in the child thread
-
traceId missing from HTTP call
If the traceId is lost, solve it one by one and never optimize it in advance
Solve the problems of MDC
The trace ID is lost in the printing of the child thread log
The traceId of the child thread will be lost in the process of printing the log. The solution is to rewrite the thread pool. For the case of directly creating a new thread, do not omit [this usage should be avoided in practical application]. Rewriting the thread pool is nothing more than a encapsulation of the task
-
Thread pool encapsulation class: 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) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } @Override public Future<?> submit(Runnable task) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } }
explain:
-
Inherit the ThreadPoolExecutor class and re execute the method of the task
-
Wrap the task once through ThreadMdcUtil
-
Thread traceId encapsulation tool 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 { return callable.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(); } }; } }
Description [take encapsulating Runnable as an example]:
-
Judge whether the MDC Map corresponding to the current thread exists, and set if it exists
-
Set the traceId value in MDC. If it does not exist, it will be generated. For the case that it is not a child thread, if it is a child thread, the traceId in MDC will not be null
-
Execute run method
The code is equivalent to the following writing method, which will be 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(); } } }; }
The wrapped Runnable is returned again. Before the task is executed, [runnable.run()] first sets the Map of the main thread to the current thread [i.e. MDC.setContextMap(context)], so that the Map corresponding to the MDC of the sub thread and the main thread is the same
traceId missing from HTTP call
When using HTTP to call the third-party service interface, the traceId will be lost, and the HTTP calling tool needs to be modified. When sending, add the traceId in the request header, add the interceptor in the lower callee, obtain the traceId in the header and add it to the MDC
There are many ways to call http. The more common ones are HttpClient, OKHttp and RestTemplate, so only the solutions of these HTTP calls are given
HttpClient:
-
Implement HttpClient interceptor
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor { @Override public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { String traceId = MDC.get(Constants.TRACE_ID); //If there is a traceId in the current thread call, the traceId will be passed through if (traceId != null) { //Add request body httpRequest.addHeader(Constants.TRACE_ID, traceId); } } }
Implement the HttpRequestInterceptor interface and rewrite the process method
If the calling thread contains traceId, you need to pass the obtained traceId down through the header in the request
-
Add interceptor for HttpClient
private static CloseableHttpClient httpClient = HttpClientBuilder.create() .addInterceptorFirst(new HttpClientTraceIdInterceptor()) .build();
Add an interceptor for HttpClient through the addInterceptorFirst method
OKHttp:
-
Implement OKHttp interceptor
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; } }
Implement the interceptor interceptor and rewrite the interceptor method. The implementation logic is similar to that of HttpClient. If the traceId of the current thread can be obtained, it will be transmitted downward
-
Add interceptors for OkHttp
private static OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new OkHttpTraceIdInterceptor()) .build();
Call the addNetworkInterceptor method to add an interceptor
RestTemplate:
-
Implement RestTemplate interceptor
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); } }
Implement the clienthttprequeinterceptor interface and rewrite the intercept method. The rest of the logic is the same, and the description is not repeated
-
Add interceptors for RestTemplate
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));
Call the setInterceptors method to add an interceptor
Third party service Interceptor:
The whole process of HTTP calling the third-party service interface requires the cooperation of the third-party service. The third-party service needs to add an interceptor to get the traceId in the request header and add it to the MDC
public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //If there is an upper layer call, use the upper layer ID 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); } }
explain:
-
Get the traceId from the request header first
-
If the traceId cannot be obtained from the request header, it indicates that it is not a third-party call and a new traceId is directly generated
-
Store the generated traceId in MDC
In addition to adding interceptors, you also need to add traceId printing in the log format, as follows:
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
% X{traceId} needs to be added
Project source code address
https://github.com/TiantianUpup/springboot-log/tree/master/springboot-trace