mdc 全局标识-实现日志链路追踪

简述

继之前说的《spring aop 自动打印接口的出入参日志,更专注于业务逻辑》,日志乃是我们排查问题的重要且主要数据之一,一个请求的完整日志能够帮我们快速定位以及分析问题的原因;我们本地跑程序,或许日志基本都会按照代码逻辑一行行的打印到终端或者文件,但测试或生产服务器环境运行的公开的多用户访问的程序,基本不会有这么有序的日志呀,基本都是多个请求的日志穿插打印,排查问题难以区分哪些参数是对应哪个请求的,哪些参数是第一哪个SQL的;所以,我们需要一个全局唯一的、贯穿整个请求业务逻辑的标识,今天我们就来讲讲MDC实现全局日志链路追踪。

(我们目前的说文都是基于spring boot项目开讲的)

1、新建一个web项目 springboot-logback-async

pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-logback-async</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-logback-async</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- 构建成可运行的Web项目 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

今天这个打印日志用到了logBack,spring boot自带了logBack的依赖,感兴趣的道友们点击 spring-boot-starter 进入查看源码依赖。

2、新建一个过滤器 MyFilter.java,在过滤器里设置全局唯一标识

今天打印日志我们从过滤器开始,不了解的道友可以稍后去看另一篇文章《spring boot 注解实现自定义Filter》。我们使用UUID作为全局唯一标识。这一步是重点。

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.util.UUID;

@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = "/*", servletNames = "*")
@Order(1)
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
      log.info("初始化我的过滤器。。。。。。。。");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //设置全局唯一标识,这里使用UUID作为唯一标识
        MDC.put("traceId", UUID.randomUUID().toString().replace("-",""));
        //放行前可在这里做一些操作
        log.info("doFilter进入过滤器操作方法。。。");
        chain.doFilter(request, response);
        // 请求返回可在这里做一些操作
        log.info("doFilter准备离开过滤器操作。。。");
        //马上返回前端,这里清除标识,避免内存资源浪费
        MDC.remove("traceId");
    }
}

3、新建一个测试接口 InitRest.java、service类,为了观看直观,我就用到了service层。

import com.example.demo.service.HelloService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class InitRest {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello() {
        log.info("进入接口。。。");
        return helloService.hello();
    }
}
public interface HelloService {
    String hello();
}
import com.example.demo.service.HelloService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class HelloServiceImpl implements HelloService {

    @Override
    public String hello() {
        log.info("进入service方法,做业务处理。。。。");
        tranDto();
        return "返回一个字符串";
    }

    private void tranDto() {
        log.info("方法调用方法tranDto。。。。");
        tranEntity();
    }
    private void tranEntity() {
        log.info("方法再调用方法tranEntity。。。。");
    }
}

4、新建一个logback.xml 文件,用于配置日志输出

我直接粘代码了,里面的语法不了解的可以先直接忽略哦,主要是是使用 %X{traceId} 这个变量打印全局唯一标识的值。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- %d日期,%t线程名,%c类的全名,%p日志级别,%file文件名,%line行数,%m%n输出的信息 -->
    <!-- 控制台输出配置 -->
    <appender name="stdout"
        class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d [%t] [%X{traceId}] [%c] [%p] (%file:%line\)- %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 日志文件配置 -->
    <appender name="baselog"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>log/run.log</File>
        <rollingPolicy
            class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>log/run.log.%d.%i</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 每一个日志文件最大size -->
                <maxFileSize>64 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 保留天数 -->
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>
                %d [%t] [%X{traceId}] [%c] [%p] - %m%n
            </pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="stdout" />
    </root>
    <!-- 定义日志输出的package包名 -->
    <logger name="com.example" level="DEBUG">
        <appender-ref ref="baselog" />
    </logger>
</configuration>

5、启动类加一个注解,OK,启动项目,访问接口,注意看日志。

@SpringBootApplication
//启用过滤器
@ServletComponentScan(basePackageClasses = {MyFilter.class})
public class SpringbootLogbackAsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootLogbackAsyncApplication.class, args);
    }
}
目录结构
启动成功
请求接口
标准输出打印全局标识
日志文件

OK,完美实现。

但,今天写这篇文章的重点,我却是另有想法,上面的做法可以解决大部分的日志混乱问题了,但是它的全局标识值只局限在当前线程,若产生子线程,则子线程的日志将缺失该标识,我苦思冥想也整不出什么好办法,希望各位道友们集思广益,帮忙出出idea,非常欢迎给我留言,抛出你的思路。

好了,若觉得文章还不错,欢迎给我留言点赞加转发!!!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容