The background,

Development problem with the most popular means of screening system is to check the system log, ELK are generally used in the distributed environment to collect the logs, but use the log when complicated with large positioning problem is more troublesome, due to a large number of other users/other threads log output through together also lead to difficult to filter out all of the specified request related log, And logs corresponding to downstream threads/services.

 

Second, the solution

  • Use one for each requestA unique identifierTo track all links displayed in the log, and do not modify the original printing mode (code intrusion free)
  • The use of LogbackMDCAdded to the mechanism log templatetraceIdIndicates that the value is%X{traceId}

MDC (Mapped Diagnostic Context) is a feature provided by LOG4J and LogBack to facilitate logging in multithreaded conditions. MDC can be thought of as a Map 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 very beginning of the request being processed.

 

Iii. Implementation of the scheme

Since MDC uses ThreadLocal internally, only the local thread is valid, and values in child threads and downstream service MDC are lost. Therefore, the main difficulty of the scheme is to solve the problem of value transfer.

3.1. Modify log templates

Logback Configuration file template format Add id %X{traceId}

 

3.2. Add a filter to the gateway

Generate a traceId and pass the header to the downstream service

@Component
public class TraceFilter extends ZuulFilter {
    @Autowired
    private TraceProperties traceProperties;

    @Override
    public String filterType(a) {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder(a) {
        return FORM_BODY_WRAPPER_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter(a) {
        // Controls whether to enable the filter according to the configuration
        return traceProperties.getEnable();
    }

    @Override
    public Object run(a) {
        // Link tracing ID
        String traceId = IdUtil.fastSimpleUUID();
        MDC.put(CommonConstant.LOG_TRACE_ID, traceId);
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.addZuulRequestHeader(CommonConstant.TRACE_ID_HEADER, traceId);
        return null; }}Copy the code

 

3.3. Add spring interceptors for downstream services

Receives and saves the traceId value interceptor

public class TraceInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String traceId = request.getHeader(CommonConstant.TRACE_ID_HEADER);
        if (StrUtil.isNotEmpty(traceId)) {
            MDC.put(CommonConstant.LOG_TRACE_ID, traceId);
        }
        return true; }}Copy the code

Register interceptors

public class DefaultWebMvcConfig extends WebMvcConfigurationSupport {
  @Override
  protected void addInterceptors(InterceptorRegistry registry) {
    // Log link tracing interceptor
    registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/ * *");

    super.addInterceptors(registry); }}Copy the code

 

3.4. Added feIGN interceptor for downstream services

Continue passing the traceId value of the current service to the downstream service

public class FeignInterceptorConfig {
    @Bean
    public RequestInterceptor requestInterceptor(a) {
        RequestInterceptor requestInterceptor = template -> {
            // Pass the log traceId
            String traceId = MDC.get(CommonConstant.LOG_TRACE_ID);
            if(StrUtil.isNotEmpty(traceId)) { template.header(CommonConstant.TRACE_ID_HEADER, traceId); }};returnrequestInterceptor; }}Copy the code

 

3.5. Extend the thread pool

Thread pools are used for business purposes (asynchronous, parallel processing), and Spring has its own @async annotation to use thread pools, so you need to extend the ThreadPoolTaskExecutor thread pool implementation to copy the PARENT thread’s MDC content to child threads

public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    /** * assigns the parent thread's MDC content to the child thread *@param runnable
     */
    @Override
    public void execute(Runnable runnable) {
        Map<String, String> mdcContext = MDC.getCopyOfContextMap();
        super.execute(() -> run(runnable, mdcContext));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        Map<String, String> mdcContext = MDC.getCopyOfContextMap();
        return super.submit(() -> call(task, mdcContext));
    }

    /** * Child delegate execution method *@param runnable {@link Runnable}
     * @paramMdcContext Parent thread MDC content */
    private void run(Runnable runnable, String tenantId, Map<String, String> mdcContext) {
        // Pass the parent thread's MDC content to the child thread
        if(mdcContext ! =null) {
            MDC.setContextMap(mdcContext);
        }
        try {
            // Perform asynchronous operations
            runnable.run();
        } finally {
            // Clear MDC contentMDC.clear(); }}/** * Child delegate execution method *@param task {@link Callable}
     * @paramMdcContext Parent thread MDC content */
    private <T> T call(Callable<T> task, Map<String, String> mdcContext) throws Exception {
        // Pass the parent thread's MDC content to the child thread
        if(mdcContext ! =null) {
            MDC.setContextMap(mdcContext);
        }
        try {
            // Perform asynchronous operations
            return task.call();
        } finally {
            // Clear MDC contentMDC.clear(); }}}Copy the code

 

4. Scene test

4.1. The test code is as follows

4.2. Logs generated by the API gateway

The gateway generated traceId value of 13 d9800c8c7944c78a06ce28c36de670

4.3. Logs generated when a request is made to jump to the file service

The traceId displayed is the same as that displayed on the gateway. The exception scenario is simulated

4.4. ELK aggregation logs passtraceIdExample Query the logs of the entire link

When the system is abnormal, you can query all the requested logs in the log center based on the traceId value of the exception log

 

Download the source code

Attached is my open source microservices framework (including the code in this article), welcome to Star

Gitee.com/zlt2000/mic…