简述
继之前说的《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,非常欢迎给我留言,抛出你的思路。
网友评论