美文网首页java开发技术
springboot+MDC打造日志链条

springboot+MDC打造日志链条

作者: source201 | 来源:发表于2022-03-17 16:38 被阅读0次

场景

在系统中,多个用户的并发请求或者多个线程的日志会交缠在一起,使得对日志的分析造成较大的困难,若能够将单个用户的请求,或者单个线程的请求用以不同的标识,在日志中标识出来,则能够更好的分析日志。

思路

要对每个请求的日志加上唯一性标识,如果纯靠手动加入,工作量大,修改麻烦,出现遗漏等等问题。而通过MDC可以解决上述问题。

本文是通过MDC来对每一个请求所经过代码加入唯一性标识,其中包含父子线程标识统一,feign跨服务调用日志统一等,来打造该请求的完整日志链条。

日志过滤器

首先通过过滤器来对每个请求加入唯一性标识。

import cn.hutool.core.util.StrUtil;
import com.demo.util.RequestIdMdcUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter(filterName = "LogFilter", urlPatterns = "/*")
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
        final HttpServletResponse response = (HttpServletResponse) res;
        final HttpServletRequest reqs = (HttpServletRequest) req;
        String requestId = reqs.getHeader(RequestIdMdcUtil.REQUEST_ID);
        if (StrUtil.isBlank(requestId)) {
            requestId = RequestIdMdcUtil.getRequestId();
        }

        MDC.put(RequestIdMdcUtil.REQUEST_ID, requestId);
        log.info("LogFilter触发request_id:{}",MDC.get(RequestIdMdcUtil.REQUEST_ID));
        try{
            chain.doFilter(req, res);
        }finally {
            MDC.remove(RequestIdMdcUtil.REQUEST_ID);
        }
    }

    @Override
    public void init(final FilterConfig filterConfig) {
        log.info("LogFilter初始化");
    }

    @Override
    public void destroy() {
        MDC.remove(RequestIdMdcUtil.REQUEST_ID);
    }
}

唯一标识工具类

import org.slf4j.MDC;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;

public class RequestIdMdcUtil {

    public static final String REQUEST_ID = "request_id";

    // 获取唯一性标识
    public static  String getRequestId(){
        return UUID.randomUUID().toString();
    }

    public static void setRequestIdIfAbsent() {
        if (MDC.get(REQUEST_ID) == null) {
            MDC.put(REQUEST_ID, getRequestId());
        }
    }
    // 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setRequestIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }

    // 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setRequestIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

日志格式设置

logging.pattern.console=[REQUESTID:%X{request_id}]%d{yyyy-MM-dd HH:mm:ss} [%t] [%-5level] %logger{96} [%M:%L] - %msg%n

以上设置,即完成了简单场景的设置

使用了线程池的场景

使用了线程池的场景,主要问题是,MDC中的数据不同线程是不共享的,因此需要将父线程中的MDC数据传入子线程中。

构建一个ThreadPoolTaskExecutor的子类

import com.demo.util.RequestIdMdcUtil;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

/**
 * @description 封装线程池任务执行器,在任务提交时,会将父线程的request_id,带入子线程
 * @author 
 * @date 2022/3/3 17:35
 */
public class ThreadPoolMdcTaskExecutor extends ThreadPoolTaskExecutor {


    public ThreadPoolMdcTaskExecutor() {
        super();
    }



    @Override
    public void execute(Runnable task) {
        super.execute(RequestIdMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }


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

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

在新建线程池的时候,new的是子类ThreadPoolMdcTaskExecutor即可。

跨服务feign调用场景

跨服务调用场景和父子线程场景类似,需要将MDC中的request_id加入到header中,然后在另一个服务中的过滤器会取出它,加入到MDC中。

@Configuration
@Slf4j
public class FeignConfig implements RequestInterceptor {

    @Override
    public void apply(final RequestTemplate template) {
        //将MDC中request_id传入header
        template.header(RequestIdMdcUtil.REQUEST_ID, MDC.get(RequestIdMdcUtil.REQUEST_ID));
    }

}

相关文章

  • springboot+MDC打造日志链条

    场景 在系统中,多个用户的并发请求或者多个线程的日志会交缠在一起,使得对日志的分析造成较大的困难,若能够将单个用户...

  • SpringBoot+MDC实现全链路调用日志跟踪

    在前面写的一篇文章中,热心网友【地藏Kelvin】评论说在多线程中还是有可能会乱掉,建议通过MDC打印traceI...

  • Linux log 信息查看 - journald

    Journald是为Linux服务器打造的新系统日志方式,它标志着文本日志文件的终结。现在日志信息写入到二进制文件...

  • filebeat+logstash+influxdb+ Graf

    filebeat+logstash+influxdb+ Grafana打造网站日志监控系统 采集数据(fliebe...

  • 曾国藩募兵

    曾国藩募兵,打造一个个阿米巴团队 横向、纵向多个链条把兵与将官牢牢拴紧 1)、军官选择标准。主要招募士绅文生,德才...

  • 2021-03-10~社群训练营Day2

    今日主题:打造社群闭环系统 红涛教练分享: 如何把社群做到闭环?小群靠玩法,大群靠设计。 第一,价值链条的设计,第...

  • 链条

    自从我给自行车链条再次上油之后,疾行的感觉又回来了。由于下雨天上学路上会有各种泥坑,采用任何技巧总是要进到其中的一...

  • 链条

    唉,我自行车的链条真是不堪一击,还真的是让人头痛。 其实我的自行车是一辆二手自行车,是我在景德镇的姑姑给我...

  • 链条

    说好的天天坚持 却无奈何地空过一天 犹如链条断了 再继续还得重新开始

  • 链条

    2017年春节的这时候,儿子特喜欢自行车,满足他起自行车的愿望买了一辆自行车。 刚刚买回家特别新鲜,骑着车去公园和...

网友评论

    本文标题:springboot+MDC打造日志链条

    本文链接:https://www.haomeiwen.com/subject/tbnvdrtx.html