Java OpenResty Spring Spring Boot MySQL Redis MongoDB PostgreSQL Linux Android Nginx 面试 小程序 Arthas JVM AQS juc Kubernetes Docker DevOps


Spring Boot Slf4j MDC 实现全链路日志追踪

Spring Boot logback 大约 2687 字

需求

请求进入容器后,注入特定的traceId,日志打印时能根据traceId关联到在ControllerServiceRepo层的日志。

Slf4j MDC

Slf4j中的MDC底层使用了ThreadLocal实现方法间传递变量。

定义 logback 配置文件

%X{traceId:-}:使用%X获取定义的traceId,如果没有赋值,则使用空字符。(配置文件中的[]可去掉,只是区分显示而已,无其他作用。)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty name="appName" source="spring.application.name"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%level] [%class : %method : %line] [%X{traceId:-}] [${appName}] : %message %n
            </pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>

    <logger name="com.example" level="DEBUG"/>
</configuration>

定义拦截器

使用MDC.put()方法存储变量,使用MDC.remove()方法移动变量。

@Component
public class TraceIdInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MDC.put("traceId", UUID.randomUUID().toString());
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        MDC.remove("traceId");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

配置拦截器

@Configuration
public class WebAppConfig implements WebMvcConfigurer {

    @Resource
    private TraceIdInterceptor traceInterceptor;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(traceInterceptor).addPathPatterns("/**");
    }

}

子线程问题

方法中使用了异步线程,需使用CopyOfContextMap复制一份MDCHashMap,在子线程中使用MDC.setContextMap设置这份拷贝的数据,就能使用异步传递了。

注意子线程中是拷贝的数据,如果在线程池中使用,需要remove

public String sayHello() {
    log.info("service");
    Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
    log.info("contextMap#{}", copyOfContextMap);
    try {
        return helloRepo.sayHello();
    } finally {
        new Thread(() -> {
            try {
                MDC.setContextMap(copyOfContextMap);
                Thread.sleep(100);
                log.info("sub thread before clear#{}", MDC.getCopyOfContextMap());
            } catch (Exception ignore) {
            } finally {
                MDC.clear();
                log.info("sub thread after clear#{}", MDC.getCopyOfContextMap());
            }
        }).start();
    }
}
阅读 123 · 发布于 2022-10-02

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb

扫描下方二维码关注公众号和小程序↓↓↓

扫描二维码关注我
昵称:
随便看看 换一批