Microservice Learning Series 13: MDC implements log link tracking

Series Article Directory


Table of contents

Series Article Directory

foreword

1. Introduction to MDC and common APIs

Introduction

API

2. Use of MDC

TraceInterceptor 

Modify logback log format 

3. The pit of MDC 


foreword

开发过程中难免遇到需要查看日志来找出问题出在哪一环节的情况,而在实际情况中服务之间互相调用所产生的日志冗长且复杂,若是再加上同一时间别的请求所产生的日志,想要精准定位自己想要查看的日志就比较麻烦。为解决此问题,遂使用MDC日志追踪。


1. Introduction to MDC and common APIs

Introduction

MDC (Mapped Diagnostic Context) is a function provided by log4j and logback to facilitate logging under multi-threaded conditions. MDC can be regarded as a Map bound to the current thread, and key-value pairs can be added to it. The content contained in the MDC can be accessed by code executing in the same thread. The child threads of the current thread will inherit the contents of the MDC in its parent thread. When logging is required, only the required information is obtained from the MDC. The content of the MDC is saved by the program at an appropriate time. For a web application, this data is usually saved at the very beginning of the request being processed.

API

  • clear() : remove all MDCs
MDC.clear();
  • get (String key): Get the value of the specified key in the current thread MDC

MDC.get("traceId");
  • put(String key, Object o) : Store the specified key-value pair in the MDC of the current thread

MDC.put("traceId", IdUtil.simpleUUID());
  • remove(String key) : delete the specified key-value pair in the current thread MDC

MDC.remove("traceId");

2. Use of MDC

  1. MDC put key-value pairs are used in interceptors. Make all requests marked with a specific identifier from the beginning.
  2. If it is a call between microservices, the upper-layer service needs to add an identifier in the header and transmit it together with the request. The lower-layer service directly uses the identifier of the upper-layer service to concatenate the logs.

TraceInterceptor 

import cn.hutool.core.util.IdUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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) throws Exception {
        String traceId = request.getHeader("requestId");
        if (StringUtils.isBlank(traceId)) {
            traceId = IdUtil.simpleUUID();
        }

        MDC.put("traceId", traceId);

        return true;
    }

    /**
     * 在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),
     * 有机会修改ModelAndView (这个博主就基本不怎么用了)
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面)
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        MDC.clear();
    }
}

Modify logback log format 

 <appender name="file-all" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/file-all.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/file-all.log.%d{yyyy-MM-dd}</fileNamePattern>
        </rollingPolicy>
        <!-- <encoder>:对记录事件进行格式化。负责两件事,一是把日志信息转换成字节数组,二是把字节数组写入到输出流。-->
        <!-- PatternLayoutEncoder 是唯一有用的且默认的encoder ,有一个<pattern>节点,用来设置日志的输入格式。使用“%”加“转换符”方式,如果要输出“%”,则必须用“\”对“\%”进行转义。-->
        <encoder>
            <pattern>[traceId:%X{traceId}]-[%d{yyyy-MM-dd HH:mm:ss:SSS}]-[%t]-[%p]-%m%n</pattern>
        </encoder>
    </appender>

final effect 

[traceId:a891644e7a844b57bf4a13c9ea40b128]-[2023-04-27 17:56:06:986]-[http-nio-7001-exec-2]-[INFO]-UserSignQueryHandler.param=UserSignQuery(type=1, userId=404278)
[traceId:a891644e7a844b57bf4a13c9ea40b128]-[2023-04-27 17:56:06:986]-[http-nio-7001-exec-2]-[INFO]-shushan.user.sign.service.getUserSign cost time 0ms
[traceId:]-[2023-04-27 17:56:13:229]-[http-nio-7001-exec-16]-[INFO]-CircleController.queryOwnerList#param=OwnerCirclePageQuery(ownerId=404278)
[traceId:c4a58ea747da4d64a4b1b4b344078a33]-[2023-04-27 17:56:13:229]-[http-nio-7001-exec-16]-[INFO]-OwnerCirclePageQueryHandler.param=OwnerCirclePageQuery(ownerId=404278)
[traceId:c4a58ea747da4d64a4b1b4b344078a33]-[2023-04-27 17:56:13:230]-[http-nio-7001-exec-16]-[DEBUG]-==>  Preparing: SELECT COUNT(*) FROM circle WHERE (owner_id = ? AND del_flag = ?)
[traceId:c4a58ea747da4d64a4b1b4b344078a33]-[2023-04-27 17:56:13:230]-[http-nio-7001-exec-16]-[DEBUG]-==> Parameters: 404278(Long), 0(Integer)
[traceId:c4a58ea747da4d64a4b1b4b344078a33]-[2023-04-27 17:56:13:234]-[http-nio-7001-exec-16]-[DEBUG]-<==      Total: 1

3. The pit of MDC 

In the main thread, if the thread pool is used, the MDC information will be lost in the thread pool;

Solution: We need to rewrite the thread pool by ourselves, get the MDC information of the main thread before calling the thread jump run, and re-put it to the sub-thread.

public class MDCThreadPoolExecutor extends ThreadPoolExecutor {

    public MDCThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public void execute(Runnable command) {
        super.execute(MDCThreadPoolExecutor.executeRunable(command, MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(MDCThreadPoolExecutor.executeRunable(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> Future<T> submit(Callable<T> callable) {
        return super.submit(MDCThreadPoolExecutor.submitCallable(callable,MDC.getCopyOfContextMap()));
    }

    public static Runnable executeRunable(Runnable runnable ,Map<String,String> mdcContext){
        return new Runnable() {
            @Override
            public void run() {
                if (mdcContext == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(mdcContext);
                }

                try {
                    runnable.run();
                } finally {
                    MDC.clear();
                }
            }
        };
    }

    private static <T> Callable<T> submitCallable(Callable<T> callable, Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }

            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }
}


Guess you like

Origin blog.csdn.net/yangyanping20108/article/details/130410286