iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > Python >SpringBoot+MDC实现链路调用日志的方法
  • 441
分享到

SpringBoot+MDC实现链路调用日志的方法

SpringBootMDC链路调用日志SpringBootMDC链路调用日志 2022-12-20 12:12:32 441人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录1.首先介绍什么是MDC1.1MDC作用2.MDC结合logback的使用demo4.多线程下MDC工具类5.自定义线程池6.线程池配置类7.测试接口8.异步的业务类1.首先介绍

1.首先介绍什么是MDC

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 WEB 应用来说,通常是在请求被处理的最开始保存这些数据

1.1MDC作用

MDC 全称是 Mapped Diagnostic Context,可以粗略的理解成是一个线程安全的存放诊断日志的容器
一般是结合log4j一起使用,为我们的日志根据线程链路加一个表示traceId,在微服务盛行的当下,链路跟踪是个难题,而借助 MDC 去埋点,巧妙实现链路跟踪应该不是问题

2.MDC结合logback的使用demo

1.logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--日志存储路径-->
    <property name="log" value="D://Xiangmu//TrackMeta//src//main//resources//log" />
    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--输出格式化-->
            <pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按天生成日志文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件名-->
            <FileNamePattern>${log}/%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
</configuration>

2.所需依赖

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.7</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

3.yml
这里需要注意swagger2和SpringBoot2的依赖问题有版本冲突,需要指定匹配策略

server:
  port: 8826
logging:
  config: classpath:logback-spring.xml
spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher  # 解决SpringBoot 和Swagger2版本冲突

4.自定义的日志拦截器
根据用户请求进行前置拦截,判断用户的请求头中是否含有TRACE_ID,如果有的话进行(当然不可能有),没有的话我们进行设置并且赋值(用UUID生成一个唯一序列放到TRACE_ID中),然后放到MDC中

后置处理器中,我们从MDC移除TRACE_ID

package com.wyh.trackmeta.interceptor;

import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.Http.httpservletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

//自定义日志拦截器:每一次链路,线程维度,添加最终的链路ID :Trace_ID
public class LogInterceptor implements HandlerInterceptor {

    private static final String TRACE_ID = "TRACE_ID";

    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String tid = UUID.randomUUID().toString().replace("-", "");
        //1.这里我们是让客户端传入链路ID,然后进行前置拦截捕获
        if(!StringUtils.isEmpty(request.getHeader("TRACE_ID"))){
            tid=request.getHeader("TRACE_ID");
        }
        //2.利用MDC将请求的上下文信息存储到当前线程的上下文映射中
        MDC.put(TRACE_ID,tid);
        return true;
    }


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

注册拦截器

package com.wyh.trackmeta.interceptor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorReGIStry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {

    //1.注册日志拦截器
    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor());
    }
}

4.多线程下MDC工具类

1.定义方法,将父线程向线程池提交任务的时候,将自身MDC中的数据复制给子线程——>存放上下文判断是否为null,不为空就将数据context放到MDC中,然后设置TraceID——>然后执行任务

总的来说就是对任务进行了一次封装

package com.wyh.trackmeta.config;

import org.slf4j.MDC;

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

public final class ThreadMdcUtil {
    private static final String TRACE_ID = "TRACE_ID";
 
    // 获取唯一性标识
    public static String generateTraceId() {
        return UUID.randomUUID().toString();
    }
 
    public static void setTraceIdIfAbsent() {
        if (MDC.get(TRACE_ID) == null) {
            MDC.put(TRACE_ID, generateTraceId());
        }
    }
 
    
    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return new Callable<T>() {
            @Override
            public T call() throws Exception {
                if (context == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(context);
                }
                setTraceIdIfAbsent();
                try {
                    return callable.call();
                } finally {
                    MDC.clear();
                }
            }
        };
    }
 
    
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

5.自定义线程池

**为什么要自定义线程池呢?**因为子线程在打印日志的过程会造成traceId丢失,解决方式就是重写线程池

package com.wyh.trackmeta.config;

import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

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

public class MyThreadPoolTaskExecutor  extends ThreadPoolTaskExecutor  {
    public MyThreadPoolTaskExecutor() {
        super();
    }
    
    @Override
    public void execute(Runnable task) {
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

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

6.线程池配置类

package com.wyh.trackmeta.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class ThreadPoolConfig {
    public static void main(String[] args) {
        Thread thread = new Thread();
    }

    
    @Bean("MyExecutor")
    public Executor asyncExecutor() {
        MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
        //核心线程数5:线程池创建时候初始化的线程数
        executor.setCorePoolSize(5);
        //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(10);
        //缓冲队列500:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.seTKEepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("asyncJCccc");
        executor.initialize();
        return executor;
    }
}

7.测试接口

package com.wyh.trackmeta.controller;

import com.wyh.trackmeta.service.UserServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "链路日志测试")
@RestController
@Slf4j
public class TestController {

    @Autowired
    private UserServiceImpl userService;

    @SneakyThrows
    @ApiOperation("日志测试Test接口")
    @RequestMapping("doTest")
    public String doTest(@RequestParam("name")String name){
       log.info("入参 name={}",name);
       testTrace();
       userService.insertUser();
       log.info("调用结束 name={}",name);
       return "Hello,"+name;
    }

    
    private void testTrace(){
        log.info("这是一行info日志");
        log.info("这是一行error日志");
        testTrace2();
    }

    private void testTrace2() {
        log.info("这也是一行日志");
    }

}

8.异步的业务类

在执行的任务方法上标注注解@Async(“线程池”)

package com.wyh.trackmeta.service;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import static java.lang.Thread.sleep;

@Slf4j
@Service
public class UserServiceImpl {
    @SneakyThrows
    @Async("MyExecutor")
    public void insertUser() throws InterruptedException {
        sleep(2000);
        log.info("正在插入数据...");
    }

}

到此这篇关于SpringBoot+MDC实现链路调用日志的文章就介绍到这了,更多相关SpringBoot MDC链路调用日志内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: SpringBoot+MDC实现链路调用日志的方法

本文链接: https://www.lsjlt.com/news/175337.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • SpringBoot+MDC实现链路调用日志的方法
    目录1.首先介绍什么是MDC1.1MDC作用2.MDC结合logback的使用demo4.多线程下MDC工具类5.自定义线程池6.线程池配置类7.测试接口8.异步的业务类1.首先介绍...
    99+
    2022-12-20
    SpringBoot MDC链路调用日志 SpringBoot MDC链路调用日志
  • 使用MDC实现日志链路跟踪
    目录1.原理2.实现3.过滤器4.logback.xml5.返回体6.效果日志前言: 在微服务环境中,我们经常使用Skywalking、CAT等去实现整体请求链路的追踪,但是这个整体...
    99+
    2024-04-02
  • SpringBoot+slf4j实现全链路调用日志跟踪的方法(一)
    SpringBoot中除了常见的分布式链路跟踪系统zipkin、skywalking等,如果需要快速定位一次请求的所有日志,那么该如何实现?实际slf4j提供了MDC(Mapped ...
    99+
    2024-04-02
  • SpringBoot 项目添加 MDC 日志链路追踪的执行流程
    目录1. 线程池配置2. 拦截器配置3. 日志文件配置4. 使用方法示例4.1. 异步使用4.2. 定时任务日志链路追踪的意思就是将一个标志跨线程进行传递,在一般的小项目中也就是在你...
    99+
    2024-04-02
  • 使用log4j MDC实现日志追踪
    目录log4j MDC实现日志追踪1、新建线程处理类 ThreadContext2、添加工具类TraceUtil3、添加ContextFilter4、在webConfiguriati...
    99+
    2024-04-02
  • SpringBootMDC全链路调用日志跟踪实现详解
    目录MDC 介绍API 说明MDC 使用添加拦截器修改日志格式常见问题子线程日志打印丢失 traceIdMDC 介绍 MDC(Mapped Diagnostic Context,映...
    99+
    2023-02-08
    SpringBoot MDC日志跟踪 SpringBoot MDC全链路调用
  • 利用Spring boot+LogBack+MDC实现链路追踪
    目录MDC介绍API说明MDC使用1.拦截器2.工具类MDC 存在的问题子线程日志打印丢失traceIdHTTP调用丢失traceId1.接口调用方2.第三方服务需要添加拦截器MDC...
    99+
    2024-04-02
  • SpringBoot+slf4j线程池全链路调用日志跟踪问题及解决思路(二)
    本项目源码已在多个项目中实践 接着上一篇文章,项目中使用了线程池,那么子线程中日志就会丢失traceId,下面讲解如何实现子线程中的traceId日志跟踪。 解决思路 子线程在打印日...
    99+
    2024-04-02
  • 微服务分布式架构实现日志链路跟踪的方法
    Logback 背景 Logback是由log4j创始人设计的另一个开源日志组件,官方网站:http://logback.qos.ch。它当前分为下面下个模块: logba...
    99+
    2024-04-02
  • Dubbo实现分布式日志链路追踪
    技术场景 在日常的开发、测试或运维的过程中,经常存在这样的场景,开发人员在代码中使用日志工具(log4j、slf4j)记录日志,比如请求ID、IP等,方便在线上快速、精准的定位问题,...
    99+
    2024-04-02
  • springboot打印接口调用日志的实例
    目录概述方案思路封装HttpServletRequest请求把可重复读请求体通过过滤器往下传记录入参日志实现入参记录拦截器注册拦截器记录返参日志概述 请求日志几乎是所有大型企业级项目...
    99+
    2024-04-02
  • springboot+HttpInvoke 实现RPC调用的方法
    开始用springboot2+hession4实现RPC服务时,发现第一个服务可以调用成功,但第二个就一直报'<'isanunknowncode。第一个服务还是...
    99+
    2024-04-02
  • 解析springboot集成AOP实现日志输出的方法
    开发接口系统中主要的一环就是日志输出,如果系统出现问题,日志能帮我们去定位问题,最常见的日志是调用方 所调用的IP 接口地址 对应方法 参数值 以及接口方接收到请求 所返回的参数。如...
    99+
    2024-04-02
  • SpringBoot整合log4j2日志的实现
    目录关于日志级别 为什么选用log4j2排除 spring-boot 自带的 logback 依赖 添加 log4j2 依赖 配置文件节点解析 根节点Configuration Ap...
    99+
    2024-04-02
  • Springboot项目处理日志的方法
    本篇内容主要讲解“Springboot项目处理日志的方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Springboot项目处理日志的方法”吧!如上图,每天会生成一个新的日志文件,然后日志进行...
    99+
    2023-06-20
  • Nginx纯配置实现日志实时上报的思路与方法
    目录前言实现思路实现步骤1. 编译 Nginx2. 配置文件如下总结前言 Nginx 作为常用的负载均衡网关. 会产生大量的日志. 但是由于 Nginx 的配置文件是一种声明式的编程...
    99+
    2024-04-02
  • Springboot使用slf4j记录日志的方法步骤
    目录前言如何在Springboot中使用slf4j记录日志测试前言 在项目开发中,记录日志是必做的一件事情。而当我们使用Springboot框架时,记录日志就变得极其简单了。 spr...
    99+
    2024-04-02
  • SpringBoot集成slf4j日志配置的方法
    目录前言 1、slf4j概述 2、pom.xml的日志依赖 3、application.yml的日志配置 4、logback.xml配置文件定义 5、logback.xml配置文件解...
    99+
    2024-04-02
  • springboot配置日志的方法是什么
    在Spring Boot中配置日志有以下几种方法:1. 使用application.properties或application.y...
    99+
    2023-08-09
    springboot
  • Springboot项目保存本地系统日志文件的实现方法
    背景:我们都知道springboot默认日志是打印在控制台中的,不会以文件的形式进行保存。那么日后系统上线肯定是有需要对日志进行定位分析问题的,那么如何实现将控制台输出的日志保存起来...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作