spring boot 日志配置traceId,实现全链路日志追踪

相关概念

traceId:用于标识某次请求的id
通过日志打印trace,能够快速定位日志中某次请求打印的所有日志,方便排查问题。

解决方案

通过配置拦截器+MDC实现,具体实现如下:

拦截器

import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;
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) {
        MDC.put("traceid", UUIDUtil.generateUuid());
        return true;
    }

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

}

唯一id生成工具类

import java.util.UUID;
public class UUIDUtil {
    public static String generateUuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

配置拦截器

@Configuration
public class InterceptorConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

logback配置

appender中日志打印格式中添加 %X{traceid},例如:

 <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread %level %c.%M:%L %X{traceid} - %msg%n</pattern>
            <charset class="java.nio.charset.Charset">UTF-8</charset>
        </encoder>
    </appender>

子线程情况

上面的配置无法将traceId 传递到子线程的日志中,如果想在子线程中打印traceId,需要使用自定义线程池,将线程的上下文传递到子线程中,线程池代码如下

public class MDCThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    /**
     * @param corePoolSize
     * @param maxPoolSize
     * @param keepAliveTime
     * @param queueCapacity
     * @param poolNamePrefix
     */

    public MDCThreadPoolTaskExecutor(int corePoolSize, int maxPoolSize,
                                     int keepAliveTime, int queueCapacity, String poolNamePrefix) {
        setCorePoolSize(corePoolSize);
        setMaxPoolSize(maxPoolSize);
        setKeepAliveSeconds(keepAliveTime);
        setQueueCapacity(queueCapacity);
        setThreadNamePrefix(poolNamePrefix);
    }

    public MDCThreadPoolTaskExecutor() {
        super();
    }

    /**
     * @return
     */
    private Map<String, String> getContextForTask() {
        return MDC.getCopyOfContextMap();
    }

    /**
     * All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)
     * all delegate to this.
     */
    @Override
    public void execute(Runnable command) {
        super.execute(wrap(command, getContextForTask()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(wrap(task, getContextForTask()));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(wrap(task, getContextForTask()));
    }

    /**
     * @param task
     * @param context
     * @param <T>
     * @return
     */
    private static <T> Callable<T> wrap(final Callable<T> task, final Map<String, String> context) {
        return () -> {
            if (context != null && !context.isEmpty()) {
                MDC.setContextMap(context);
            }

            try {
                return task.call();
            } finally {
                if (context != null && !context.isEmpty()) {
                    MDC.clear();
                }
            }
        };
    }

    /**
     * @param runnable
     * @param context
     * @return
     */
    private static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context != null && !context.isEmpty()) {
                MDC.setContextMap(context);
            }

            try {
                runnable.run();
            } finally {
                if (context != null && !context.isEmpty()) {
                    MDC.clear();
                }
            }
        };
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值