在日常排查问题时,log.info输出的日志并没有唯一标识,导致实际排查问题时无法判断某一条日志是否是当前线程请求输出的日志。而我们可以通过拦截器拦截日志输出时设置一个唯一标识到输出中,后续只需要排查一下请求时的报文就可以获取到唯一标识,然后再通过唯一标识拿到所有的请求日志信息,方便排查问题。

一、工程依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

二、日志输出配置

日志输出配置通常包含三部分:Level(日志输出级别)、Appender(输出到哪里)、Layout(输出格式)。logback中将Appender的作用放大了,除了指定输出位置之外,还能配置输出策略以及输出格式。这节只需要对appender的子元素pattern进行调整,增加输出%X{logId}。%X可以引用日志上下文的信息,这些信息是保存在MDC中的。示例配置如下:

<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <!--格式化输出:%d表示日期,%X{logId}表示日志唯一编号 %thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{logId}] [%thread] %-5level %logger{60}[%L] - %msg%n</pattern>
    </encoder>
</appender>

三、日志拦截器

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
 

public class LogInterceptor extends HandlerInterceptorAdapter {
    Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
 
    private final static String LOG_ID_KEY = "logId";
 
    @Override
    public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception {
        try {
            // 删除logId
            MDC.remove(LOG_ID_KEY);
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
        }
    }
 
    @Override
    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
                           Object arg2, ModelAndView arg3) throws Exception {
    }
 
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
 
        try {
            // 设置logId
            String logId = UUID.randomUUID().toString().replace("-", "");
            MDC.put(LOG_ID_KEY, logId);
            return true;
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            return false;
        }
    }
}

四、加入拦截器链

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
 
    /**
     * 把日志拦截器注入为bean
     *
     * @return
     */
    @Bean
    public HandlerInterceptor logInterceptor() {
        return new LogInterceptor();
    }
 
    /**
     * 注册拦截器
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns 用于添加拦截规则, 这里假设拦截 /url 后面的全部链接
        // excludePathPatterns 用户排除拦截
        registry.addInterceptor(logInterceptor()).addPathPatterns("/**");
    }
}

到此,springboot工程可以对每次接口调用产生的日志都标记唯一的编号。