MDC实现日志链路追踪

/ 工具和中间件 / 2 条评论 / 2338浏览

MDC实现日志链路追踪

一个线程安全的存放诊断日志的容器,基于ThreadLocal 来进行的实现

快速入门

@Slf4j
@SpringBootTest
class ApplicationTests {

    private final String REQ_ID = "REQ_ID";

    @Test
    void contextLoads() {
        MDC.put(REQ_ID, UUID.randomUUID().toString());
        log.info("开始调用服务A,进行业务处理");
        log.info("业务处理完毕,可以释放空间了,避免内存泄露");
        MDC.remove(REQ_ID);
        log.info("REQ_ID 还有吗?{}", MDC.get(REQ_ID) != null);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="30 minutes">
    <property name="logDir" value="${catalina.base}/logs" />
    <!-- 打印到控制台 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{yyyy-MM-dd HH🇲🇲ss.SSS} [%X{REQ_ID}] [%thread] %-5level-%logger{5} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="stdout" />
    </root>
</configuration>
2021-10-28 13:15:14.944 [e72f3a7f-b836-4909-827b-a6363ee8ba81] [main] INFO -c.z.ApplicationTests - 开始调用服务A,进行业务处理
2021-10-28 13:15:14.944 [e72f3a7f-b836-4909-827b-a6363ee8ba81] [main] INFO -c.z.ApplicationTests - 业务处理完毕,可以释放空间了,避免内存泄露
2021-10-28 13:15:14.944 [] [main] INFO -c.z.ApplicationTests - REQ_ID 还有吗?false

项目应用

/**
 * @author wj
 * @since 2021-10-27 13:37
 * 请求头添加traceId拦截器,注意TRACE_ID用完要记得remove掉
 */
@Component
public class TraceIdInterceptor implements HandlerInterceptor {

    private final String TRACE_ID = "TRACE_ID";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String traceId = getTraceId(request);
        //将TRACE_ID添加到MDC中(注意TRACE_ID用完要记得remove掉)
        MDC.put(TRACE_ID, traceId);
        //将TRACE_ID添加进响应头
        response.addHeader(TRACE_ID, traceId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在请求完成后 删除掉TRACE_ID
        MDC.remove(TRACE_ID);
    }

    /**
     * 生成一个唯一id(url + uuid)
     * @param request
     * @return
     */
    private String getTraceId(HttpServletRequest request){
        return String.format("%s-%s",request.getRequestURI(), UUID.randomUUID().toString().replace("-", ""));
    }
/**
 * @author wj
 * @since 2021-10-27 13:56
 * web请求config类
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Autowired
    private TraceIdInterceptor traceIdInterceptor;

    /**
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //请求头添加traceId拦截器,在请求头中增加traceId
        registry.addInterceptor(traceIdInterceptor)
                .addPathPatterns("/**");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="30 minutes">
    <property name="logDir" value="${catalina.base}/logs" />
    <!-- 打印到控制台 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{yyyy-MM-dd HH🇲🇲ss.SSS} [%X{REQ_ID}] [%thread] %-5level-%logger{5} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="debuglog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->
        <file>${logDir}/debug.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/debug_%d{yyyy-MM-dd}.log%i.zip</fileNamePattern>
            <!-- 限制文件最大保存时间为15天; 15*24=360 -->
            <maxHistory>360</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 当文件大小超过60M时触发滚动,这里设置60M -->
                <maxFileSize>60MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date{yyyy-MM-dd HH🇲🇲ss.SSS} [%X{REQ_ID}] [%thread] %-5level-%logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="infolog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->
        <file>${logDir}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/info_%d{yyyy-MM-dd-a}.log%i.zip</fileNamePattern>
            <!-- 限制文件最大保存时间为15天; 15*24=360 -->
            <maxHistory>360</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 当文件大小超过60M时触发滚动,这里设置60M -->
                <maxFileSize>60MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%date{yyyy-MM-dd HH🇲🇲ss.SSS} [%X{REQ_ID}] [%thread] %-5level-%logger{5} - %msg%n</pattern>
        </encoder>
        <!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 用于配置不符合过滤条件的操作 DENY:日志将立即被抛弃不再经过其他过滤器 -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="errorlog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->
        <file>${logDir}/error.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/error_%d{yyyy-MM-dd}.log%i.zip</fileNamePattern>
            <!-- 限制文件最大保存时间为15天; 15*24=360 -->
            <maxHistory>360</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 当文件大小超过60M时触发滚动,这里设置60M -->
                <maxFileSize>60MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date{yyyy-MM-dd HH🇲🇲ss.SSS} [%X{REQ_ID}] [%thread] %-5level-%logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="debuglog" />
        <appender-ref ref="infolog" />
        <appender-ref ref="errorlog" />
    </root>

</configuration>

非请求情况使用MDC(如:定时任务)

    @Scheduled(cron = "0 0/1 * * * ?")
    public void serverInfo() {
        //将TRACE_ID添加到MDC中(注意TRACE_ID用完要记得remove掉)
        MDC.put("TRACE_ID", BaseIdUtils.uuid());
        
        业务逻辑处理...
        
        //将TRACE_ID从mdc中清除掉,否则容易导致内存溢出
        MDC.remove("TRACE_ID");
    }