在pom中引入这些内容
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!-- 引入log4j日志时需去掉默认的logback -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 日志管理log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--引入aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
创建log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info">
<Properties>
<!-- 声明日志文件存储的目录 -->
<Property name="LOG_HOME">E:/logs/app/</Property>
<Property name="APP_NAME">log</Property>
<Property name="LOG_PATTERN"
value="[请求id=%X{requestId}] %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread][%class{36}:%line] - %msg%n"></Property>
</Properties>
<Appenders>
<!--输出控制台的配置-->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<!--输出日志到文件的配置,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面-->
<RollingFile name="RollingFile" fileName="${LOG_HOME}/${APP_NAME}.log"
filePattern="${LOG_HOME}/%d{yyyy-MM}/%d{yyyy-MM-dd}_%i.log">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<!-- 最多保留文件数 -->
<DefaultRolloverStrategy max="365"/>
</RollingFile>
</Appenders>
<!--然后定义Logger,只有定义了Logger并引入的Appender,Appender才会生效。Root中level配置了日志级别,可配置其他级别-->
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</configuration>
主要关注这里%X{requestId},动态传参,接收MDC中的key的值,打印在控制台上
创建AOP实现全局header中的参数拦截,放入MDC中
package com.example.kucun.aop;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyMDC {
//请求id key
private static final String KEY="requestId";
@Autowired
private HttpServletRequest request;
/**
* 环绕通知 监听controller
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("execution(* com.example.kucun.controller.*.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//返回结果
Object result = null;
//从header头中获取请求id
String requestId = request.getHeader("requestId");
//执行之前添加请求id
MDC.put(KEY,requestId);
try {
//执行方法
result = proceedingJoinPoint.proceed();
return result;
} catch (Throwable throwable) {
throw throwable;
} finally {
//方法执行完毕要释放请求id
MDC.remove(KEY);
}
}
}
创建控制层
package com.example.kucun.controller;
import com.example.kucun.service.KuCunService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class KcController {
@GetMapping("/aa")
public String aa(){
log.info("我是aa");
return "aa";
}
@GetMapping("/bb")
public String bb(String requestId){
log.info("我是bb");
new Thread(new Task(()->{
log.info("我是异步日志");
})).start();
return "bb";
}
}
线程和线程池打印日志的时候,要使用装饰器模式进行封装一下,否则不会打印出来
package com.example.kucun.controller;
import org.slf4j.MDC;
import java.util.Map;
public class Task implements Runnable{
private Runnable runnable;
private Map<String,String> map;
public Task(Runnable runnable){
this.runnable=runnable;
//保存当前线程的mdc值
this.map= MDC.getCopyOfContextMap();
}
@Override
public void run() {
//传入已经保存mdc的值
for (Map.Entry<String, String> entry : map.entrySet()) {
MDC.put(entry.getKey(),entry.getValue());
}
//装饰器模式执行run
runnable.run();
//移除已保存的mdc值
for (Map.Entry<String, String> entry : map.entrySet()) {
MDC.remove(entry.getKey());
}
}
}
前端vue在请求的时候,可以在header中传入参数
后端控制台就显示这个请求id的数据,然后就可以通过这个请求id
去排查当前的请求他一共调用了多少个接口